NousResearch/hermes-agent

Sandbox writable-path fixes for Kanban and NixOS

Cluster 26203 · agent/transports. Data as of

2 open items. Fix merged in #28435 on 19 May 2026. Check it covers your case.

The short answer

1open issue
1open PR
7items grouped
Known?
Yes. 7 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #28435. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this unresolved. That is separate from the fix event.
Still open
1 issue and 1 PR.

These issues and pull requests address cases where sandboxed environments deny write access to necessary paths, specifically Kanban workspace and board DB roots in the Codex app-server and working directories in the NixOS systemd service module.

Open issues1

  • Issue#27941[Bug]: codex_app_server Kanban workers cannot write artifacts to HERMES_KANBAN_WORKSPACE when it differs from cwd

Open PRs1

  • PR#34637fix(agent): grant Kanban workspace to Codex sandbox

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #27557 fix(codex): scope kanban worker writable root in app-server sandbox (salvage #26212)

    merged to main

    files: agent/transports/codex_app_server.py, scripts/release.py, tests/agent/transports/test_codex_app_server_runtime.py, website/docs/user-guide/features/codex-app-server-runtime.md

    Show the diff
    • agent/transports/codex_app_server.py
      @@ -74,12 +74,43 @@ def __init__(
               env: Optional[dict[str, str]] = None,
           ) -> None:
               self._codex_bin = codex_bin
      -        cmd = [codex_bin, "app-server"] + list(extra_args or [])
               spawn_env = os.environ.copy()
               if env:
                   spawn_env.update(env)
               if codex_home:
                   spawn_env["CODEX_HOME"] = codex_home
      +
      +        app_server_args = list(extra_args or [])
      +        # Kanban workers must be able to write their handoff/status back to
      +        # the board DB, which lives outside the per-task workspace. Keep the
      +        # Codex sandbox on, but add the Kanban root as the only extra writable
      +        # root. Without this, codex-runtime workers finish their actual work
      +        # but crash/block when kanban_complete/kanban_block writes SQLite.
      +        if spawn_env.get("HERMES_KANBAN_TASK"):
      +            kanban_db = spawn_env.get("HERMES_KANBAN_DB")
      +            kanban_root = (
      +                os.path.dirname(kanban_db)
      +                if kanban_db
      +                else spawn_env.get(
      +                    "HERMES_KANBAN_ROOT",
      +                    os.path.join(
      +                        spawn_env.get("HERMES_HOME", os.path.expanduser("~/.hermes")),
      +                        "kanban",
      +                    ),
      +                )
      +            )
      +            app_server_args.extend(
      +                [
      +                    "-c",
      +                    'sandbox_mode="workspace-write"',
      +                    "-c",
      +                    f'sandbox_workspace_write.writable_roots=["{kanban_root}"]',
      +                    "-c",
      +                    "sandbox_workspace_write.network_access=false",
      +                ]
      +            )
      +
      +        cmd = [codex_bin, "app-server"] + app_server_args
               # Codex emits tracing to stderr; default WARN keeps it quiet for users.
               spawn_env.setdefault("RUST_LOG", "warn")
       
    • scripts/release.py
      @@ -1159,6 +1159,8 @@
           "[email protected]": "bird",  # PR #25219 (gateway docker exit-75 restart)
           "[redacted:email]": "davidcampbelldc",  # PR #26834 (web_server proxy_headers=False)
           "[email protected]": "davidcampbelldc",
      +    "[redacted:email]": "hehehe0803",  # PR #26212 salvage (codex kanban writable root)
      +    "[email protected]": "hehehe0803",
       }
       
       
    • tests/agent/transports/test_codex_app_server_runtime.py
      @@ -241,3 +241,58 @@ def kill(self):
               assert captured["env"].get("CODEX_HOME") == "/tmp/profile/codex"
               # And HOME still passes through unchanged
               assert captured["env"].get("HOME") == "/users/alice"
      +
      +    def test_kanban_worker_adds_only_kanban_writable_root(self, monkeypatch):
      +        """Codex-runtime Kanban workers need to write board state outside
      +        their scratch/worktree workspace, but should not fall back to
      +        danger-full-access. Hermes passes a narrow app-server config override
      +        for the Kanban root only.
      +        """
      +        import subprocess
      +        from agent.transports import codex_app_server as cas
      +
      +        captured = {}
      +
      +        class FakePopen:
      +            def __init__(self, cmd, *args, **kwargs):
      +                captured["cmd"] = list(cmd)
      +                captured["env"] = kwargs.get("env", {}).copy()
      +                self.stdin = None
      +                self.stdout = None
      +                self.stderr = None
      +                self.pid = 1
      +                self.returncode = None
      +
      +            def poll(self):
      +                return None
      +
      +            def terminate(self):
      +                pass
      +
      +            def wait(self, timeout=None):
      +                return 0
      +
      +            def kill(self):
      +                pass
      +
      +        monkeypatch.setattr(subprocess, "Popen", FakePopen)
      +        monkeypatch.setenv("HOME", "/users/alice")
      +        monkeypatch.setenv("HERMES_HOME", "/users/alice/.hermes/profiles/backend-worker")
      +        monkeypatch.setenv("HERMES_KANBAN_TASK", "t_smoke")
      +        monkeypatch.setenv(
      +            "HERMES_KANBAN_DB",
      +            "/users/alice/.hermes/kanban/boards/smoke/kanban.db",
      +        )
      +
      +        client = cas.CodexAppServerClient(codex_bin="codex")
      +        client._closed = True
      +
      +        cmd = captured["cmd"]
      +        assert cmd[:2] == ["codex", "app-server"]
      +        assert 'sandbox_mode="workspace-write"' in cmd
      +        assert (
      +            'sandbox_workspace_write.writable_roots=["/users/alice/.hermes/kanban/boards/smoke"]'
      +            in cmd
      +        )
      +        assert "sandbox_workspace_write.network_access=false" in cmd
      +        assert all("danger" not in part for part in cmd)
    • website/docs/user-guide/features/codex-app-server-runtime.md
      @@ -91,11 +91,11 @@ What works inside a codex-runtime worker:
       - The Hermes tool callback for browser_*, vision, image_gen, skills, TTS
       
       What also works because the MCP callback exposes them:
      -- **`kanban_complete` / `kanban_block` / `kanban_comment` / `kanban_heartbeat`** — the worker handoff tools. These read `HERMES_KANBAN_TASK` from env (set by the dispatcher), gate access correctly, and write to `~/.hermes/kanban.db`. Without these in the callback, a worker on this runtime could do its task but couldn't report back, hanging until the dispatcher's timeout.
      +- **`kanban_complete` / `kanban_block` / `kanban_comment` / `kanban_heartbeat`** — the worker handoff tools. These read `HERMES_KANBAN_TASK` from env (set by the dispatcher), gate access correctly, and write to the per-board SQLite DB pinned by `HERMES_KANBAN_DB`. Without these in the callback, a worker on this runtime could do its task but couldn't report back, hanging until the dispatcher's timeout.
       - **`kanban_show` / `kanban_list`** — read-only board queries for the worker to check its own context.
       - **`kanban_create` / `kanban_unblock` / `kanban_link`** — orchestrator-only operations. Available for orchestrator agents running on the codex runtime that need to dispatch new tasks.
       
      -The kanban tools are gated by `HERMES_KANBAN_TASK` env var the dispatcher sets — that var is propagated to the codex subprocess (codex inherits env) and from there to the spawned `hermes-tools` MCP server subprocess. So the tools see the right task id and gate correctly.
      +The kanban tools are gated by `HERMES_KANBAN_TASK` env var the dispatcher sets — that var is propagated to the codex subprocess (codex inherits env) and from there to the spawned `hermes-tools` MCP server subprocess. So the tools see the right task id and gate correctly. For Codex app-server workers, Hermes also passes narrow app-server sandbox overrides when `HERMES_KANBAN_TASK` is present: keep `workspace-write` sandboxing, add only the current board directory (derived from `HERMES_KANBAN_DB`) as an extra writable root, and keep network disabled by default. This avoids the brittle `:danger-no-sandbox` workaround while letting `kanban_complete` / `kanban_block` update the board DB.
       
       ### Cron jobs
       
  • #28435 fix(codex_app_server): include every Kanban-pinned path in writable_roots (#27941) (#27950)

    merged to main

    files: website/docs/user-guide/features/codex-app-server-runtime.md

2 PRs closed without merging

From the close comments: done elsewhere 1 · merged via another change 1

  • #26212 fix(codex): allow sandboxed Kanban worker handoffs · merged via another change → 4a7cd2e, #27557
  • #41905 fix(agent): resume codex app-server thread across turns to preserve session context · done elsewhere → b5fcf63, 85cb1dc, 35cea9b, #116343, #115759, #116586
Closed issues · 1
  • Closed#26203Codex app-server Kanban workers cannot complete tasks under sandbox without board DB writable root
How this cluster was grouped
  • Link evidence (partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
  • Members outside that structural core may belong to a different underlying problem.
  • Pairwise link density 0.26: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
agent/transports
Merged PRs
#27557, #28435
Closed issues
1
Attempts
2 PRs closed without merging
Fix link checked
19 May 2026

For your agent

The same facts, typed.