Skip to content

fix(install): quote AGENT_BROWSER_EXECUTABLE_PATH for spaced paths - #57249

Open
pnascimento9596 wants to merge 2 commits into
NousResearch:mainfrom
pnascimento9596:fix/installer-quote-browser-env-path
Open

fix(install): quote AGENT_BROWSER_EXECUTABLE_PATH for spaced paths#57249
pnascimento9596 wants to merge 2 commits into
NousResearch:mainfrom
pnascimento9596:fix/installer-quote-browser-env-path

Conversation

@pnascimento9596

@pnascimento9596 pnascimento9596 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Fixes #57247

Problem

See #57247. The installer persists the user's explicit browser override into ~/.hermes/.env unquoted. Spaced paths (macOS app bundles, Windows Program Files) produce a line that fails POSIX shell source, leaving the var empty with a stderr error.

Opt-in only: post-#50852 the installer no longer auto-detects a browser. Default installs are unaffected.

Why naive quoting is wrong

Quoting the value is exactly what makes python-dotenv escape-decode it, so the value must be escaped. Naive double-quoting corrupts real Windows paths under python-dotenv 1.2.2:

Path written as "..." (no escapes) After dotenv_values
C:\Users\p\scoop\apps\chrome.exe C:\Users\p\scoop + BEL + pps\chrome.exe (\a → BEL)
D:\bin\firefox.exe D: + BS + in + FF + irefox.exe (\b/\f control chars)
E:\tools\tbrowser.exe E: + TAB + ools + TAB + browser.exe (\t → tab)

Core constraint

python-dotenv escape-decodes double-quoted values while sh expands $/backtick, and dotenv leaves \$ literal, so no double-quoted form is safe for both readers.

Escaped double quotes are correct for Hermes because:

  1. python-dotenv is the reader Hermes actually uses (hermes_cli/env_loader.py)
  2. It is the dialect _quote_env_value already writes (hermes_cli/config.py)

Single quotes are shell-perfect but silently drop apostrophe paths under python-dotenv.

Changes

  • scripts/install.sh:244 - dotenv_double_quote helper next to json_escape (backslash + " escape, then wrap in ")
  • scripts/install.sh:2118 - writer uses the helper instead of raw echo
  • scripts/install.sh:1888,1892 - snap strip regex generalized to ['\"]?/snap/ (tolerates ', ", or unquoted)
  • scripts/install.sh:1861 - find_system_browser already rejects /snap/*, so quoted-snap stripping is defensive migration only
  • scripts/install.ps1:340 - ConvertTo-DotEnvDoubleQuoted using ordinal .Replace (not -replace; regex would mangle backslashes)
  • scripts/install.ps1:354,359 - both Write-BrowserEnv writer sites use the helper
  • tests/test_install_sh_browser_install.py - behavioral coverage via supported --stage config / --stage node-deps boundaries (no source reading; per AGENTS.md)

Testing

Boundary-driven tests (no installer source extraction):

scripts/run_tests.sh tests/test_install_sh_browser_install.py -q

On this head (ee3b81c4a): 14 collected - 12 passed, 2 xfailed (strict) documenting the $/backtick shell limitation.

Coverage:

  • 6 dotenv + POSIX sh round-trips (spaces, Windows backslash paths, apostrophe, embedded ")
  • 2 dotenv-only cases for literal $ / backtick paths
  • 2 strict xfails: POSIX sh cannot round-trip $ / backtick under any dotenv-compatible double-quoted form
  • 3 snap-cleanup cases (unquoted / single / double) via --stage node-deps
  • 1 PATH autodetection negative via --stage config
  • stderr preserved; rc == 0 and empty stderr asserted on installer and sh source

Also verified:

  • PowerShell helper executed under pwsh 7.4.6 with python-dotenv round-trip (Gate A: 7/7)
  • GitHub CI on ee3b81c4a: 23 SUCCESS / 7 SKIPPED / 1 NEUTRAL

Limitation

$ / backtick paths remain shell-source hostile in any dotenv-compatible double-quoted form. Documented by strict xfail tests; dotenv still preserves the literal path.

Platforms tested

  • macOS: real installer stages + full test module
  • pwsh 7.4.6: ConvertTo-DotEnvDoubleQuoted executed with dotenv round-trip
  • Windows PowerShell 5.1: not run on Windows

Update (2026-08-04): rebased onto upstream/main
(36cb5ae) following the triage disposition
on #57247 recommending this PR as the fix. Installer
fix content is unchanged (per-file patch-id verified
on scripts/install.sh and scripts/install.ps1). The
test file was merged with the current upstream suite:
all upstream tests retained, this PR's round-trip
suite added, and 4 earlier interim tests dropped
as superseded (mapping in the table below). All
checks green at 697baf1. Ready for review.

Dropped interim test Covering successor Behavior covered
test_quoted_spaced_browser_path_round_trips_under_bash test_config_stage_browser_path_round_trips_through_dotenv_and_sh Spaced browser path survives write + shell source (bash)
test_quoted_spaced_browser_path_round_trips_under_sh test_config_stage_browser_path_round_trips_through_dotenv_and_sh Spaced browser path survives write + shell source (POSIX sh)
test_quoted_browser_env_write_is_double_quoted test_config_stage_browser_path_round_trips_through_dotenv_and_sh (via _expected_serialized_line) .env line is double-quoted with backslash and quote escapes
test_install_script_strips_stale_snap_browser_override test_node_deps_stage_strips_quoted_snap_override Stale Snap override stripped for unquoted/single/double forms
_run_configure_and_source (helper) _run_config_stage + _source_with_posix_sh Source-extract harness replaced by supported stage boundary

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard area/config Config system, migrations, profiles tool/browser Browser automation (CDP, Playwright) labels Jul 2, 2026
@pnascimento9596

Copy link
Copy Markdown
Contributor Author

Adding the rationale for the double-quote form, since it wasn't spelled out in
the original description and it's the deciding detail for Hermes specifically.

Hermes reads AGENT_BROWSER_EXECUTABLE_PATH through python-dotenv
(load_hermes_dotenv() -> dotenv.load_dotenv(), then browser_tool.py reads
os.environ) — it doesn't shell-source .env at agent startup. Both quote
styles round-trip fine for the spaced paths this bug is about (macOS
Chrome.app, Windows Program Files, /snap/...). They only diverge on paths
containing an apostrophe:

  • ="/path/O'Brian/chrome" (double quotes) parses under both python-dotenv
    and shell source.
  • ='/path/O'\''Brian/chrome' (single-quote splice) sources fine, but fails
    to parse under python-dotenv
    — the key drops and the browser override
    silently disappears (reproduced on python-dotenv 1.2.2).

The double-quote form also matches Hermes' own .env writer (_quote_env_value
in hermes_cli/config.py), so the installer stays consistent with how the rest
of the codebase quotes values.

Flagging for #57449 as well (same fix, single-quote helper) — happy to fold
embedded-quote handling into this PR if consolidating is preferred.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for tracing all four installer writes and keeping the Snap cleanup aligned.

Problems

  • scripts/install.sh:2109 and scripts/install.ps1:347,352 insert the raw path between double quotes. Unlike the existing .env serializer (hermes_cli/config.py:7622), they do not escape embedded " or \\; a valid POSIX executable path containing " yields malformed sourced .env syntax.
  • tests/test_install_sh_browser_install.py:337 suppresses source stderr with 2>&1, and the tests do not assert the returned rc or stderr. This does not verify the stated no-stderr contract.
  • The new harness reads and regex-extracts install.sh (tests/test_install_sh_browser_install.py:307), contrary to the source-reading-test prohibition in AGENTS.md:1358-1411.

Suggested changes

  • Add proper double-quote/backslash escaping and an embedded-double-quote round-trip case.
  • Preserve stderr and assert rc == 0 plus stderr == "".
  • Move the serialization behind a callable helper or test the supported installer boundary rather than extracting source text.

Automated hermes-sweeper review.

Comment thread scripts/install.sh Outdated
Comment thread tests/test_install_sh_browser_install.py Outdated
Comment thread tests/test_install_sh_browser_install.py Outdated
@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 15, 2026
@pnascimento9596

Copy link
Copy Markdown
Contributor Author

The earlier comment said the double-quote form "matches _quote_env_value"; it did not—the canonical writer escapes \ and ", while the patch did neither, so the review is right.

Reproduced with python-dotenv 1.2.2 before this fix:

Raw value written between "..." Parsed result
C:\Users\p\scoop\apps\chrome.exe C:\Users\p\scoop\x07pps\chrome.exe (\a became BEL)
D:\bin\firefox.exe D:\x08in\x0cirefox.exe (\b/\f became control characters)
C:\Program Files\Google\Chrome\Application\chrome.exe unchanged
browser "beta" exe parse failure; key dropped

The underlying constraint is that python-dotenv escape-decodes double-quoted values, while sh expands $ and backticks and python-dotenv leaves \$ literal. There is no double-quoted encoding that safely preserves those characters for both readers. Escaped double quotes are the correct choice here because Hermes actually reads this file through python-dotenv, and this is the dialect _quote_env_value already writes. #57449's single-quote form is shell-perfect, but it silently drops apostrophe-containing paths under python-dotenv.

The honest limitation remains: paths containing $ or backticks are still shell-source-hostile. That behavior is covered by documented xfail tests rather than hidden.

All three review points are addressed: both installers now escape backslashes and double quotes; source stderr is preserved and the tests assert return code and stderr; and the source-extraction harness is gone—the tests drive the real writer through the supported --stage config / --stage node-deps boundary required by AGENTS.md:1358-1411.

CI on ee3b81c4a: 23 SUCCESS / 7 SKIPPED / 1 NEUTRAL.

@pnascimento9596

Copy link
Copy Markdown
Contributor Author

Note for reviewers: the Jul 26 head move (ee3b81c4a to 3fcfa6dc6) was a rebase onto current main pushed by a local sync tool. The fix commits are patch-identical (range-diff clean), so the three review threads above show as outdated purely because of the rebase, not because of new changes. All three were addressed before the rebase and remain addressed.

…aths source cleanly

configure_browser_env_from_system_browser writes the
browser override as AGENT_BROWSER_EXECUTABLE_PATH=$browser_path
with no surrounding quotes. Post-NousResearch#50852 this path is
opt-in only: it runs when the user has exported
AGENT_BROWSER_EXECUTABLE_PATH explicitly. When that
override contains a space (macOS
/Applications/Google Chrome.app/Contents/MacOS/Google Chrome,
Windows C:\Program Files\...), the written line is
invalid under POSIX shell source: the shell parses the
first word after the space as a command, emits
"no such file or directory", and leaves the var empty.
install.ps1 has the same bug at both writer sites.

The Hermes runtime is unaffected (it loads .env via
python-dotenv, which parses the unquoted value), but
anyone who sources ~/.hermes/.env from a shell —
which override users are especially likely to do —
gets a stderr error and a silently-empty var.

Fix: wrap the value in double quotes at all four
writer sites (install.sh + install.ps1). Generalize
the strip_snap_browser_override regexes from
'^AGENT_BROWSER_EXECUTABLE_PATH=/snap/' to
'="?/snap/' so the Snap-stripper still fires on the
new quoted form — without this the migration silently
no-ops on a quoted snap path, a regression the naive
one-line fix would introduce. Update the test
assertion pinning the old regex and add behavioral
tests that write a spaced path and re-source the .env
under both bash and POSIX sh.
Mirror the canonical _quote_env_value escaping in both installers and replace source-extraction tests with supported-stage behavior coverage.

Accept hand-written single- or double-quoted Snap overrides defensively. The writer itself cannot emit a Snap path because find_system_browser rejects /snap/*.
@pnascimento9596
pnascimento9596 force-pushed the fix/installer-quote-browser-env-path branch from 3fcfa6d to 697baf1 Compare August 5, 2026 00:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles area/install-update Installer, updater, packaging, wheels, doctor comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows tool/browser Browser automation (CDP, Playwright) type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Installer writes unquoted AGENT_BROWSER_EXECUTABLE_PATH override into .env — spaced paths break shell source

3 participants