Skip to content

fix(install.ps1): drop UTF-8 BOM that breaks the irm | iex one-liner (#27397)#27419

Closed
xxxigm wants to merge 2 commits into
NousResearch:mainfrom
xxxigm:fix/27397-install-ps1-bom
Closed

fix(install.ps1): drop UTF-8 BOM that breaks the irm | iex one-liner (#27397)#27419
xxxigm wants to merge 2 commits into
NousResearch:mainfrom
xxxigm:fix/27397-install-ps1-bom

Conversation

@xxxigm

@xxxigm xxxigm commented May 17, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes the canonical Windows one-line installer

irm https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.ps1 | iex

which was failing on every fresh Windows 11 box with

The assignment expression is not valid. The input to an assignment operator must be
an object that is able to accept assignments, such as a variable or a property.

at the [string]$Branch = "main" line inside the param() block, before doing any work.

Root cause

scripts/install.ps1 had accidentally picked up a leading UTF-8 BOM (EF BB BF) at byte 0:

$ file scripts/install.ps1
scripts/install.ps1: Unicode text, UTF-8 (with BOM) text
  • PowerShell's script-file loader strips the BOM, so .\install.ps1 worked fine.
  • irm | iex decodes the body into a .NET string, the BOM survives as U+FEFF at position 0, and [scriptblock]::Create() does not strip it.
  • With a stray U+FEFF in front of param(, PowerShell stops recognising param as a keyword. Each line of the block then parses as a bare assignment — [string]$Branch = "main" becomes type-cast-on-LHS = string-on-RHS, which is exactly the "invalid assignment expression" error in [Bug]: One Line Install not working #27397.

The script's own comment even claimed "the file remains pure ASCII for PS 5.1 parser compatibility" — that contract was silently broken, almost certainly by an editor save defaulting to "UTF-8 with signature". The two other .ps1 files in the repo (acp_adapter/bootstrap/bootstrap_browser_tools.ps1, scripts/tests/test-install-ps1-stage-protocol.ps1) start with a plain # — only this one carried a BOM.

The fix

  1. Re-save scripts/install.ps1 as pure ASCII (no BOM). After: file reports ASCII text and byte 0 is 0x23 (#).
  2. Expand the in-file comment header into a loud "FILE ENCODING REQUIREMENT" block at the top of the file explaining the failure mode and the rule, so a future editor save doesn't quietly reintroduce the regression.
  3. Add a Python CI test tests/scripts/test_install_ps1_encoding.py that pins the byte invariants — no UTF-8 BOM, no UTF-16 BOM, pure ASCII, byte 0 is #, param( is the first executable line, and the documented defaults ($Branch, $HermesHome, $InstallDir) are intact.

Related Issue

Closes #27397.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • scripts/install.ps1 — file re-saved as pure ASCII (no BOM); the 13-line "FILE ENCODING REQUIREMENT" block added at the top explains why and points future editors at the regression test. No behavioural code change — the param() block, defaults, and 2138-line installer body are byte-identical to before, only the leading 3 BOM bytes were dropped and a comment block was added.
  • tests/scripts/test_install_ps1_encoding.py — 8 new regression tests:
    • TestInstallPs1ByteInvariants — 4 cases: no UTF-8 BOM (the exact [Bug]: One Line Install not working #27397 regression), no UTF-16 LE/BE BOM (the Set-Content default on PS 5.1, even worse for iex), pure ASCII (catches smart quotes / em dashes), first line is a comment or param.
    • TestParamBlockStillPresent — 2 cases: param( is the first executable line, and the documented defaults $Branch = "main", $HermesHome = "$env:LOCALAPPDATA\hermes", $InstallDir = "$env:LOCALAPPDATA\hermes\hermes-agent" are intact so a future refactor doesn't drop them and reintroduce the symptom in a different shape.
    • TestSimulateIrmIexParsing — 2 cases: byte 0 must be # (0x23) and the first meaningful codepoint must be # or p — the class of guarantee that irm | iex parsing relies on.

How to Test

  1. Check out this branch and ensure .venv is set up: python3 -m venv .venv && source .venv/bin/activate && pip install -e ".[all,dev]"
  2. Run the new tests on their own:
    scripts/run_tests.sh tests/scripts/test_install_ps1_encoding.py -v
    
    Expected: 8 passed.
  3. Sensitivity check (the test must actually catch the regression):
    python -c "
    from pathlib import Path
    p = Path('scripts/install.ps1')
    orig = p.read_bytes()
    try:
        p.write_bytes(b'\xef\xbb\xbf' + orig)
        import subprocess
        r = subprocess.run(['python', '-m', 'pytest', 'tests/scripts/test_install_ps1_encoding.py', '-q'], capture_output=True, text=True)
        print(r.stdout[-400:])
    finally:
        p.write_bytes(orig)
    "
    
    Expected: 7 of 8 tests fail with UTF-8 BOM / expected '#' (0x23) messages.
  4. Manual Windows smoke (PowerShell 5.1 or 7, requires a Windows box):
    • Pre-fix (main branch): irm https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.ps1 | iex raises The assignment expression is not valid at the [string]$Branch = "main" line.
    • With this branch's install.ps1 served at the same URL: the param block parses cleanly and the installer proceeds normally.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(install.ps1): ..., test(install.ps1): ...)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix (no unrelated commits)
  • I've run scripts/run_tests.sh tests/scripts/test_install_ps1_encoding.py and all tests pass
  • I've added tests for my changes (8 new regression tests, inverted-fix sensitivity verified: 7 of 8 fail when the BOM is re-introduced)
  • I've tested on my platform: macOS 15.2 (Darwin 24.6.0), Python 3.12. The bug itself is Windows-only but the fix is byte-level and platform-independent.

Documentation & Housekeeping

  • I've updated relevant documentation (scripts/install.ps1 now carries a "FILE ENCODING REQUIREMENT" comment block at the top documenting the rule and pointing at the CI test)
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — the only on-wire change is "drop 3 BOM bytes". .\install.ps1 direct invocation, pwsh 7+, and Windows PS 5.1 all parse the BOM-less file identically; the regression only existed for the BOM + iex-decoded-string path. macOS / Linux don't run this file.
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

Screenshots / Logs

$ file scripts/install.ps1
scripts/install.ps1: ASCII text, with very long lines (363)

$ head -c 8 scripts/install.ps1 | xxd
00000000: 2320 3d3d 3d3d 3d3d                      # ======

$ scripts/run_tests.sh tests/scripts/test_install_ps1_encoding.py -v
8 passed in 1.49s

Sensitivity check (BOM re-injected, then restored):

$ # ... inject BOM ...
7 failed, 1 passed in 1.59s
FAILED tests/scripts/test_install_ps1_encoding.py::TestInstallPs1ByteInvariants::test_does_not_start_with_utf8_bom
FAILED tests/scripts/test_install_ps1_encoding.py::TestSimulateIrmIexParsing::test_byte_zero_is_safe_for_iex
FAILED tests/scripts/test_install_ps1_encoding.py::TestSimulateIrmIexParsing::test_first_meaningful_codepoint_is_safe
FAILED tests/scripts/test_install_ps1_encoding.py::TestInstallPs1ByteInvariants::test_is_pure_ascii
FAILED tests/scripts/test_install_ps1_encoding.py::TestInstallPs1ByteInvariants::test_first_line_is_a_comment_or_param
FAILED tests/scripts/test_install_ps1_encoding.py::TestParamBlockStillPresent::test_param_block_at_top_level
FAILED tests/scripts/test_install_ps1_encoding.py::TestParamBlockStillPresent::test_param_block_defaults_intact

xxxigm added 2 commits May 17, 2026 18:24
The Windows one-line installer

    irm https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.ps1 | iex

was failing on every fresh Windows 11 box with

    The assignment expression is not valid. The input to an assignment
    operator must be an object that is able to accept assignments, such
    as a variable or a property.

at the `[string]$Branch = "main"` line of the `param()` block.

Root cause: `scripts/install.ps1` had accidentally picked up a leading
UTF-8 BOM (`EF BB BF`) at byte 0. PowerShell's *script-file* loader
strips that BOM, so `.\install.ps1` worked. But `irm | iex` decodes the
bytes into a .NET string, the BOM survives as U+FEFF at position 0,
and `[scriptblock]::Create()` does NOT strip it. With a stray U+FEFF in
front of `param(`, PowerShell stops recognising `param` as a keyword,
each line of the block parses as a bare assignment, and the whole
installer aborts before doing any work.

Fixes the issue by re-saving the file as pure ASCII (no BOM), exactly
matching the contract the in-file comment already claimed
("the file remains pure ASCII for PS 5.1 parser compatibility").

Also expands that comment into a loud "FILE ENCODING REQUIREMENT" block
at the top so a future editor save -- especially in a Windows editor
that defaults to "UTF-8 with signature" -- doesn't silently reintroduce
the regression.

Closes NousResearch#27397.
…bytes

8 regression tests that pin the on-disk byte invariants of
scripts/install.ps1 so a future editor save can't silently bring
back the NousResearch#27397 symptom:

* TestInstallPs1ByteInvariants -- no UTF-8 BOM, no UTF-16 LE/BE BOM,
  pure ASCII, first line is `#` or `param`.
* TestParamBlockStillPresent -- `param(` is the first executable line
  and the documented defaults ($Branch, $HermesHome, $InstallDir) are
  intact, so a future refactor doesn't drop them and reintroduce the
  symptom in a different shape.
* TestSimulateIrmIexParsing -- byte 0 must be `#` and the first
  meaningful codepoint must be `#` or `p`. A surprise character there
  is exactly the class of bug that NousResearch#27397 hit.

Verified by inverting the fix: temporarily prepending `EF BB BF` back
onto scripts/install.ps1 makes 7 of 8 tests fail.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard labels May 17, 2026
@JeremyDev87

Copy link
Copy Markdown

ddalggak review — not merge-ready due current CI, but targeted fix looks valid.

Blocking first: current PR status still has the GitHub test check failing, so I would not mark this ready until CI is green or maintainers explicitly classify the failure as unrelated baseline noise.

Evidence checked:

  • origin/main:scripts/install.ps1 starts with UTF-8 BOM: ef bb bf ...
  • PR head 7d4541e549f665577dbc5660b0cb5b08fc22c084 starts with # and has no UTF-8/UTF-16 BOM.
  • Targeted local test: python3 -m pytest tests/scripts/test_install_ps1_encoding.py -q8 passed
  • GitHub Tests / test currently reports failure (21 failed, 23590 passed, 147 skipped); observed failures appear outside scripts/install.ps1, but they still leave the PR unstable.

Scope assessment:

  • The code change directly addresses [Bug]: One Line Install not working #27397's irm | iex / BOM parse failure without changing installer behavior beyond encoding/top comment.
  • The new test is strict: ASCII-only and byte 0 must be #. That matches this script's install-one-liner contract, but it is worth keeping intentional.

Conclusion: fix the failing test status or document it as unrelated baseline before readiness.

@0thernes 0thernes left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$u = 'https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.ps1'
$p = Join-Path $env:TEMP 'hermes-install.ps1'
Invoke-WebRequest -Uri $u -OutFile $p
powershell.exe -ExecutionPolicy Bypass -File $p

This works just fine.

@teknium1

Copy link
Copy Markdown
Contributor

Closing as superseded by #28169.

Triage notes (high confidence):
scripts/install.ps1 on origin/main no longer starts with EF BB BF (first bytes are '# ==='); merged PR #28169 'feat(install.ps1): strip BOM, add -Commit/-Tag pin params, harden git ops' (2026-05-19) already stripped the BOM.

Thanks for the contribution — the underlying problem this PR addresses has been resolved by the linked PR on current main. If you believe this was closed in error, please comment and we'll reopen.

(Bulk-closed during a CLI PR triage sweep.)

@teknium1 teknium1 closed this May 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: One Line Install not working

5 participants