hermes - ✅(Solved) Fix terminal.docker_extra_args from config.yaml silently dropped — missing in _terminal_env_map bridge [3 pull requests, 2 comments, 2 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#28863Fetched 2026-05-20 04:01:25
View on GitHub
Comments
2
Participants
2
Timeline
13
Reactions
0
Author
Participants
Timeline (top)
labeled ×6cross-referenced ×5commented ×2

The _terminal_env_map dict in gateway/run.py (the bridge of yaml.terminal.* keys → TERMINAL_* env vars at gateway startup) omits docker_extra_args. As a result, anything set via hermes config set terminal.docker_extra_args [...] or written directly to config.yaml is silently ignored by terminal_tool, which reads only from the TERMINAL_DOCKER_EXTRA_ARGS env var.

This makes the documented Docker hardening config path non-functional.

Error Message

The agent reports this back to the user as an unrelated "permission denied" error and the silent-drop is invisible without inspecting the run_args log line.

Root Cause

gateway/run.py defines _terminal_env_map (around the _terminal_cfg = _cfg.get("terminal", {}) block). It includes most terminal.* keys (e.g. docker_image, docker_volumes, docker_env, docker_run_as_host_user, docker_mount_cwd_to_workspace, container_cpu/memory/disk/persistent) but not docker_extra_args.

tools/terminal_tool.py _get_env_config() then reads:

"docker_extra_args": _parse_env_var("TERMINAL_DOCKER_EXTRA_ARGS", "[]", json.loads, "valid JSON"),

Since the env var is never set by the bridge, the function returns [], and the user's hardening config is dropped without any warning.

docker_forward_env appears to have the same gap.

Fix Action

Workaround

Set the env var directly in ~/.hermes/.env:

TERMINAL_DOCKER_EXTRA_ARGS=["--read-only","--tmpfs","/root:rw,exec,size=1g,mode=1777"]

terminal_tool picks it up. Restart gateway. To verify it actually reached docker run, grep Docker run_args: in agent.log — the extra flags should appear at the end of the list.

PR fix notes

PR #28888: fix(terminal): add docker_extra_args to all three config-to-env bridge maps (#28863)

Description (problem / solution / changelog)

Summary

Add docker_extra_args to all three config→env bridge maps so terminal.docker_extra_args from config.yaml actually reaches the terminal tool.

Fixes #28863

Problem

terminal.docker_extra_args in config.yaml is silently dropped. The terminal tool reads TERMINAL_DOCKER_EXTRA_ARGS (terminal_tool.py L1093), but none of the three bridge paths populated it:

  1. gateway/run.py _terminal_env_map — gateway/messaging platform startup
  2. cli.py env_mappings — CLI/TUI startup
  3. hermes_cli/config.py _config_to_env_synchermes config set one-shot

This is the same class of bug as docker_run_as_host_user and docker_mount_cwd_to_workspace before it — a new config key added to the terminal defaults (config.py L654) without updating all bridge maps.

Fix

Add "docker_extra_args": "TERMINAL_DOCKER_EXTRA_ARGS" to all three dicts. Three files, one line each.

Testing

  • Existing sync test (test_terminal_config_env_sync.py::test_cli_and_gateway_env_maps_agree) now passes (was failing before this fix)
  • 29 terminal config tests all pass, 0 failures

Changed files

  • cli.py (modified, +1/-0)
  • gateway/run.py (modified, +1/-0)
  • hermes_cli/config.py (modified, +1/-0)

PR #28891: fix(gateway): bridge terminal.docker_extra_args and docker_forward_env to env

Description (problem / solution / changelog)

gateway/run.py's _terminal_env_map omitted docker_extra_args and docker_forward_env, so values set via hermes config set or written directly to config.yaml were silently dropped before terminal_tool could read the corresponding env vars. The documented Docker hardening path (--read-only, --security-opt, custom --tmpfs) was non-functional on the YAML path; operators had to bypass it by exporting env vars in .env directly.

This adds both keys to the bridge dict and to _config_to_env_sync in hermes_cli/config.py so config set writes them correctly too.

Fixes #28863

Changed files

  • cli.py (modified, +1/-0)
  • gateway/run.py (modified, +1/-0)
  • hermes_cli/config.py (modified, +2/-0)
  • tests/hermes_cli/test_set_config_value.py (modified, +14/-0)
  • tests/tools/test_terminal_config_env_sync.py (modified, +91/-0)

PR #28995: feat: Config-Runtime Contract Registry (Phase 1) + fix #28046 + fix #28863

Description (problem / solution / changelog)

Summary

Implements Phase 1 of the Typed Config-Runtime Contract — a declarative config binding registry that catches orphan config fields at startup, before users discover them silently.

Along the way, fixes two concrete bugs that motivated this work.

Bug Fixes

Fix #28046: max_tokens silently ignored from custom_providers per-model config

Problem: Only context_length was read from custom_providers[].models.<model>. The max_tokens field was silently ignored, always defaulting to 4096.

Fix: Extract get_custom_provider_model_field() as a generic lookup helper, add get_custom_provider_max_tokens() symmetric to context_length, and read it in agent_init.py after the existing context_length lookup.

Fix #28863: terminal.docker_extra_args silently dropped

Problem: docker_extra_args was declared in DEFAULT_CONFIG but missing from the _terminal_env_map bridge in gateway/run.py, so it was never passed to the terminal tool.

Fix: Add the mapping entry.

Feature: Config-Runtime Contract Registry (Phase 1)

New module: hermes_cli/config_contracts.py

  • CONFIG_BINDINGS — declarative registry of 29 config→runtime bindings (all terminal.* env vars, model.max_tokens, model.context_length, custom_providers wildcards)
  • get_nested() — resolves dotted keys with wildcard support, including list-of-dicts (for custom_providers)
  • validate_config_bindings() — cross-checks loaded config against registry, returns warning list
  • get_binding_report() — human-readable status report for debugging

Gateway integration

At startup, after env var bridging, the gateway runs validate_config_bindings() and logs any warnings. This is purely additive — warnings only, no behavior change, wrapped in try/except so it can never block startup.

How this prevents future bugs

When a developer adds a new terminal.* key to DEFAULT_CONFIG, the contract registry immediately shows it as unregistered. The next person who reviews the code sees the gap before shipping.

Test Coverage

Test fileTestsStatus
test_config_contracts.py17All pass
test_custom_provider_max_tokens.py10All pass
test_custom_provider_context_length.py12All pass (existing, unchanged)
Total39All pass

Backward Compatibility

  • get_custom_provider_context_length() signature unchanged
  • get_custom_provider_max_tokens() is a new function (purely additive)
  • config_contracts.py is a new module (purely additive)
  • Gateway validation is wrapped in try/except (cannot break startup)
  • Zero behavior change to any existing code path

Related

  • Implements Phase 1 of #28984
  • Complements #27342 (config schema validation — different scope)
  • Supersedes PR #28988 (this PR includes its fix plus the contract layer)

Files Changed

FileChange
hermes_cli/config_contracts.pyNew — Contract registry module (235 lines)
tests/hermes_cli/test_config_contracts.pyNew — 17 tests
tests/hermes_cli/test_custom_provider_max_tokens.pyNew — 10 tests
agent/agent_init.py+14 — Read max_tokens from custom_providers
hermes_cli/config.py+66/-25 — Generic helper + max_tokens function
gateway/run.py+14 — docker_extra_args mapping + contract validation

Changed files

  • .dev-workflow/code-graph.db (added, +0/-0)
  • agent/agent_init.py (modified, +14/-0)
  • gateway/run.py (modified, +13/-0)
  • hermes_cli/config.py (modified, +66/-25)
  • hermes_cli/config_contracts.py (added, +235/-0)
  • tests/hermes_cli/test_config_contracts.py (added, +142/-0)
  • tests/hermes_cli/test_custom_provider_max_tokens.py (added, +158/-0)

Code Example

terminal:
     backend: docker
     docker_extra_args:
       - "--read-only"
       - "--tmpfs"
       - "/root:rw,exec,size=1g,mode=1777"

---

grep 'Docker run_args' ~/.hermes/logs/agent.log | tail -1

---

bash: line N: cd: /root: Permission denied

---

"docker_extra_args": _parse_env_var("TERMINAL_DOCKER_EXTRA_ARGS", "[]", json.loads, "valid JSON"),

---

"docker_extra_args": "TERMINAL_DOCKER_EXTRA_ARGS",
"docker_forward_env": "TERMINAL_DOCKER_FORWARD_ENV",

---

TERMINAL_DOCKER_EXTRA_ARGS=["--read-only","--tmpfs","/root:rw,exec,size=1g,mode=1777"]
RAW_BUFFERClick to expand / collapse

terminal.docker_extra_args from config.yaml silently dropped

Affected: Hermes Agent v0.14.0 (tag v2026.5.16). Confirmed still present in main HEAD as of 2026-05-19 (hermes update pulled 27 new commits since v2026.5.16; the bug is unchanged).

Summary

The _terminal_env_map dict in gateway/run.py (the bridge of yaml.terminal.* keys → TERMINAL_* env vars at gateway startup) omits docker_extra_args. As a result, anything set via hermes config set terminal.docker_extra_args [...] or written directly to config.yaml is silently ignored by terminal_tool, which reads only from the TERMINAL_DOCKER_EXTRA_ARGS env var.

This makes the documented Docker hardening config path non-functional.

Reproduction

  1. Configure the Docker backend and add hardening flags via the documented yaml path. Because hermes config set doesn't accept list values cleanly (a separate issue), edit ~/.hermes/config.yaml directly:
    terminal:
      backend: docker
      docker_extra_args:
        - "--read-only"
        - "--tmpfs"
        - "/root:rw,exec,size=1g,mode=1777"
  2. Restart gateway: hermes gateway restart --system.
  3. Have the agent invoke any terminal command (or trigger one via hermes chat -q "выполни id").
  4. Inspect the resulting docker run invocation in ~/.hermes/logs/agent.log:
    grep 'Docker run_args' ~/.hermes/logs/agent.log | tail -1

Expected: --read-only and the custom --tmpfs flags appear at the end of the run_args list (extra_args is appended last per tools/environments/docker.py).

Actual: They are absent. The container starts without --read-only and with the default Hermes /root tmpfs (no mode=1777 override). If the image's /root inherits mode 0700 and the container runs as a non-root user, every command then fails with:

bash: line N: cd: /root: Permission denied

The agent reports this back to the user as an unrelated "permission denied" error and the silent-drop is invisible without inspecting the run_args log line.

Root cause

gateway/run.py defines _terminal_env_map (around the _terminal_cfg = _cfg.get("terminal", {}) block). It includes most terminal.* keys (e.g. docker_image, docker_volumes, docker_env, docker_run_as_host_user, docker_mount_cwd_to_workspace, container_cpu/memory/disk/persistent) but not docker_extra_args.

tools/terminal_tool.py _get_env_config() then reads:

"docker_extra_args": _parse_env_var("TERMINAL_DOCKER_EXTRA_ARGS", "[]", json.loads, "valid JSON"),

Since the env var is never set by the bridge, the function returns [], and the user's hardening config is dropped without any warning.

docker_forward_env appears to have the same gap.

Fix (1–2 lines)

Add to _terminal_env_map in gateway/run.py:

"docker_extra_args": "TERMINAL_DOCKER_EXTRA_ARGS",
"docker_forward_env": "TERMINAL_DOCKER_FORWARD_ENV",

hermes_cli/config.py _config_to_env_sync (used by hermes config set) should match.

Why this matters

docker_extra_args is the only documented escape hatch for additional docker run flags: runtime hardening (--read-only, --security-opt, custom --tmpfs overrides), per-image quirks, anything Hermes doesn't model directly. Silently dropping it forces operators to bypass the documented path entirely (manually exporting env vars in .env), and the lack of any warning makes the failure hide behind unrelated downstream errors (container permission denials at runtime).

Workaround

Set the env var directly in ~/.hermes/.env:

TERMINAL_DOCKER_EXTRA_ARGS=["--read-only","--tmpfs","/root:rw,exec,size=1g,mode=1777"]

terminal_tool picks it up. Restart gateway. To verify it actually reached docker run, grep Docker run_args: in agent.log — the extra flags should appear at the end of the list.

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