hermes - ✅(Solved) Fix [Bug]: Headless VM fails to use browser: tools/browser_tool.py writes AGENT_BROWSER_CHROME_FLAGS but agent-browser reads AGENT_BROWSER_ARGS --sandbox-bypass injection is a no-op [1 pull requests, 1 comments, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#23496Fetched 2026-05-11 03:29:13
View on GitHub
Comments
1
Participants
1
Timeline
10
Reactions
0
Participants
Timeline (top)
cross-referenced ×4labeled ×4commented ×1renamed ×1

The Chrome launch-flag injection in tools/browser_tool.py::_run_browser_command writes the subprocess env var AGENT_BROWSER_CHROME_FLAGS, but agent-browser does not read that variable. The documented env var for browser launch args is AGENT_BROWSER_ARGS. As a result the --no-sandbox workaround for root execution and Ubuntu/AppArmor unprivileged-userns restrictions (issue #15765, PR #15771, commit 74c1b946e) is a silent no-op

Result: Chromium still fails to launch with "No usable sandbox" on affected systems.

Error Message

Inject browser launch args via AGENT_BROWSER_ARGS env var (issue #15765).

Reads from config["browser"]["launch_args"] first; falls back to

"--no-sandbox --disable-dev-shm-usage" when running as root or on Ubuntu

23.10+/AppArmor systems where unprivileged user namespaces are restricted.

Skipped for CDP/cloud sessions where Chrome runs remotely.

_chrome_launch_args: str = "" if not session_info.get("cdp_url"): try: from hermes_cli.config import read_raw_config as _read_cfg cfg = _read_cfg() user_args = cfg_get(cfg, "browser", "launch_args") or "" if user_args.strip(): _chrome_launch_args = user_args.strip() else: _needs_sandbox_bypass = False if hasattr(os, "geteuid") and os.geteuid() == 0: _needs_sandbox_bypass = True logger.debug("browser: running as root — defaulting --no-sandbox") else: _userns_restrict = "/proc/sys/kernel/apparmor_restrict_unprivileged_userns" try: with open(_userns_restrict, encoding="utf-8") as _f: if _f.read().strip() == "1": _needs_sandbox_bypass = True logger.debug( "browser: AppArmor userns restrictions detected — " "defaulting --no-sandbox" ) except OSError: pass if _needs_sandbox_bypass: _chrome_launch_args = "--no-sandbox --disable-dev-shm-usage" except Exception as e: logger.debug("Could not read browser launch args from config: %s", e)

Root Cause

Root Cause Analysis (optional)

Fix Action

Fix / Workaround

The Chrome launch-flag injection in tools/browser_tool.py::_run_browser_command writes the subprocess env var AGENT_BROWSER_CHROME_FLAGS, but agent-browser does not read that variable. The documented env var for browser launch args is AGENT_BROWSER_ARGS. As a result the --no-sandbox workaround for root execution and Ubuntu/AppArmor unprivileged-userns restrictions (issue #15765, PR #15771, commit 74c1b946e) is a silent no-op

PR fix notes

PR #23517: fix(browser): pass sandbox flags via AGENT_BROWSER_ARGS

Description (problem / solution / changelog)

What does this PR do?

Fixes the sandbox-bypass env handoff in tools/browser_tool.py so local browser launches pass Chromium flags through the env var that agent-browser actually reads.

Today Hermes detects when --no-sandbox --disable-dev-shm-usage is needed, but writes those flags to AGENT_BROWSER_CHROME_FLAGS. agent-browser expects AGENT_BROWSER_ARGS, so the bypass never reaches Chrome and headless launches still fail on root/AppArmor-restricted hosts.

Related Issue

Fixes #23496

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

  • switch the sandbox bypass env injection from AGENT_BROWSER_CHROME_FLAGS to AGENT_BROWSER_ARGS in tools/browser_tool.py
  • add regression coverage in tests/tools/test_browser_homebrew_paths.py for both root and AppArmor-restricted launches
  • assert we no longer emit the stale AGENT_BROWSER_CHROME_FLAGS env var in the launch path

How to Test

  1. Run uv run --frozen pytest -q -o addopts='' tests/tools/test_browser_homebrew_paths.py
  2. Confirm the new tests cover root sandbox bypass and AppArmor userns restriction detection
  3. Optionally reproduce on a Linux/AppArmor host and verify browser commands now launch with AGENT_BROWSER_ARGS=--no-sandbox --disable-dev-shm-usage

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS 26.4.1

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

Screenshots / Logs

  • uv run --frozen pytest -q -o addopts='' tests/tools/test_browser_homebrew_paths.py -> 24 passed
  • uv run --frozen ruff check tools/browser_tool.py tests/tools/test_browser_homebrew_paths.py -> passed

Changed files

  • tests/tools/test_browser_homebrew_paths.py (modified, +78/-0)
  • tools/browser_tool.py (modified, +2/-2)

Code Example

Report       https://paste.rs/eHL5S
agent.log    https://dpaste.com/HXJU6TZBX
gateway.log  https://dpaste.com/EUDBVMBFH

---



---

# Inject browser launch args via AGENT_BROWSER_ARGS env var (issue #15765).
# Reads from config["browser"]["launch_args"] first; falls back to
# "--no-sandbox --disable-dev-shm-usage" when running as root or on Ubuntu
# 23.10+/AppArmor systems where unprivileged user namespaces are restricted.
# Skipped for CDP/cloud sessions where Chrome runs remotely.
_chrome_launch_args: str = ""
if not session_info.get("cdp_url"):
    try:
        from hermes_cli.config import read_raw_config as _read_cfg
        cfg = _read_cfg()
        user_args = cfg_get(cfg, "browser", "launch_args") or ""
        if user_args.strip():
            _chrome_launch_args = user_args.strip()
        else:
            _needs_sandbox_bypass = False
            if hasattr(os, "geteuid") and os.geteuid() == 0:
                _needs_sandbox_bypass = True
                logger.debug("browser: running as root — defaulting --no-sandbox")
            else:
                _userns_restrict = "/proc/sys/kernel/apparmor_restrict_unprivileged_userns"
                try:
                    with open(_userns_restrict, encoding="utf-8") as _f:
                        if _f.read().strip() == "1":
                            _needs_sandbox_bypass = True
                            logger.debug(
                                "browser: AppArmor userns restrictions detected — "
                                "defaulting --no-sandbox"
                            )
                except OSError:
                    pass
            if _needs_sandbox_bypass:
                _chrome_launch_args = "--no-sandbox --disable-dev-shm-usage"
    except Exception as e:
        logger.debug("Could not read browser launch args from config: %s", e)

---

# Set AGENT_BROWSER_ARGS so agent-browser passes flags to Chrome on launch.
if _chrome_launch_args and "AGENT_BROWSER_ARGS" not in os.environ:
    browser_env["AGENT_BROWSER_ARGS"] = _chrome_launch_args
RAW_BUFFERClick to expand / collapse

Bug Description

Summary

The Chrome launch-flag injection in tools/browser_tool.py::_run_browser_command writes the subprocess env var AGENT_BROWSER_CHROME_FLAGS, but agent-browser does not read that variable. The documented env var for browser launch args is AGENT_BROWSER_ARGS. As a result the --no-sandbox workaround for root execution and Ubuntu/AppArmor unprivileged-userns restrictions (issue #15765, PR #15771, commit 74c1b946e) is a silent no-op

Result: Chromium still fails to launch with "No usable sandbox" on affected systems.

Steps to Reproduce

  1. Ubuntu 23.10+ host with /proc/sys/kernel/apparmor_restrict_unprivileged_userns = 1 (default on 24.04 LTS).
  2. Non-root user runs hermes browser_navigate https://example.com (or any other browser_* tool).
  3. The auto-detect block in _run_browser_command correctly identifies that sandbox bypass is needed and sets AGENT_BROWSER_CHROME_FLAGS=--no-sandbox --disable-dev-shm-usage in browser_env before the subprocess Popen.
  4. agent-browser does not consult AGENT_BROWSER_CHROME_FLAGS. Chromium launches with the default sandbox enabled and exits with "No usable sandbox". The user-facing error is the agent-browser failure; the upstream auto-detect succeeds in its own log line, masking the cause.

Expected Behavior

When running on Ubuntu 23.10+ (or any system with AppArmor unprivileged-userns restrictions), the browser tool should successfully launch Chrome in sandbox-bypass mode (--no-sandbox --disable-dev-shm-usage) and render pages without error. A user asking Hermes to take a screenshot of a website should get a successful response with the captured image.

Actual Behavior

The browser tool fails to launch Chrome entirely. The user-facing error is:

Chrome exited early (exit code: unknown) without writing DevToolsActivePort No usable sandbox!

The upstream auto-detect block in _run_browser_command correctly identifies that sandbox bypass is needed, but the env var it writes (AGENT_BROWSER_CHROME_FLAGS) is never read by agent-browser, which expects AGENT_BROWSER_ARGS. The bypass injection is therefore a silent no-op — Chrome launches with its default sandbox and crashes on AppArmor-restricted systems.

Affected Component

Tools (terminal, file ops, web, code execution, etc.)

Messaging Platform (if gateway-related)

No response

Debug Report

Report       https://paste.rs/eHL5S
agent.log    https://dpaste.com/HXJU6TZBX
gateway.log  https://dpaste.com/EUDBVMBFH

Operating System

Ubuntu 24.04

Python Version

3.11.15

Hermes Version

v0.13.0 (2026.5.7)

Additional Logs / Traceback (optional)

Root Cause Analysis (optional)

No response

Proposed Fix (optional)

Replace the env-var-writing block inside _run_browser_command with a layout that (a) writes to the correct env var, (b) computes the flag once before the subprocess plumbing rather than mid-try:, and (c) gives users a config-driven override via [browser] launch_args. Skip entirely for CDP/cloud sessions where Chromium runs remotely.

Between the cmd_prefix block and the cmd_parts = ... assignment, add:

# Inject browser launch args via AGENT_BROWSER_ARGS env var (issue #15765).
# Reads from config["browser"]["launch_args"] first; falls back to
# "--no-sandbox --disable-dev-shm-usage" when running as root or on Ubuntu
# 23.10+/AppArmor systems where unprivileged user namespaces are restricted.
# Skipped for CDP/cloud sessions where Chrome runs remotely.
_chrome_launch_args: str = ""
if not session_info.get("cdp_url"):
    try:
        from hermes_cli.config import read_raw_config as _read_cfg
        cfg = _read_cfg()
        user_args = cfg_get(cfg, "browser", "launch_args") or ""
        if user_args.strip():
            _chrome_launch_args = user_args.strip()
        else:
            _needs_sandbox_bypass = False
            if hasattr(os, "geteuid") and os.geteuid() == 0:
                _needs_sandbox_bypass = True
                logger.debug("browser: running as root — defaulting --no-sandbox")
            else:
                _userns_restrict = "/proc/sys/kernel/apparmor_restrict_unprivileged_userns"
                try:
                    with open(_userns_restrict, encoding="utf-8") as _f:
                        if _f.read().strip() == "1":
                            _needs_sandbox_bypass = True
                            logger.debug(
                                "browser: AppArmor userns restrictions detected — "
                                "defaulting --no-sandbox"
                            )
                except OSError:
                    pass
            if _needs_sandbox_bypass:
                _chrome_launch_args = "--no-sandbox --disable-dev-shm-usage"
    except Exception as e:
        logger.debug("Could not read browser launch args from config: %s", e)

Then, inside the existing try: block where browser_env = {**os.environ} is built, replace the current if "AGENT_BROWSER_CHROME_FLAGS" not in browser_env: ... block with:

# Set AGENT_BROWSER_ARGS so agent-browser passes flags to Chrome on launch.
if _chrome_launch_args and "AGENT_BROWSER_ARGS" not in os.environ:
    browser_env["AGENT_BROWSER_ARGS"] = _chrome_launch_args

Notes for reviewers:

  • The detection logic (root via os.geteuid(), AppArmor via /proc/sys/kernel/apparmor_restrict_unprivileged_userns) is unchanged from the current block — only its position in the function moves.
  • The guard "AGENT_BROWSER_ARGS" not in os.environ lets operators override via shell or systemd Environment= without code edits.
  • Adding [browser] launch_args to the config gives users a documented escape hatch for setting arbitrary Chrome flags (e.g. --disable-gpu, custom flags for testing) without needing to touch source.
  • A simpler in-place rename of AGENT_BROWSER_CHROME_FLAGS to AGENT_BROWSER_ARGS within the current block was not sufficient to restore launches in our testing; we'd encourage maintainers to reproduce on an Ubuntu 24.04 AppArmor host before considering a minimal rename as the fix.

Are you willing to submit a PR for this?

  • I'd like to fix this myself and submit a PR

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING