# NousResearch/hermes-agent · Sandbox writable-path fixes for Kanban and NixOS

## Open now

- 1 issue, 1 PR open on GitHub.
- #27941: \[Bug\]: codex_app_server Kanban workers cannot write artifacts to HERMES_KANBAN_WORKSPACE when it differs from cwd
- PR #34637: fix(agent): grant Kanban workspace to Codex sandbox

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.

## TL;DR

- 7 member(s): 1 open issue(s), 1 closed issue(s), 1 open PR(s), 2 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #28435. Verify it addresses your case.
- **Recommended:** read PR #28435 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `unresolved`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): 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 member 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.

## Open related issues

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

## Closed / duplicate issues

- #26203: Codex app-server Kanban workers cannot complete tasks under sandbox without board DB writable root

## Prior & competing PRs

- **#27557: fix(codex): scope kanban worker writable root in app-server sandbox (salvage #26212)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - 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`
  - fix hunks:
  - `agent/transports/codex_app_server.py`

    ```diff
    @@ -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`

    ```diff
    @@ -1159,6 +1159,8 @@
         "6666242+bird@users.noreply.github.com": "bird",  # PR #25219 (gateway docker exit-75 restart)
         "[redacted:email]": "davidcampbelldc",  # PR #26834 (web_server proxy_headers=False)
         "165905879+davidcampbelldc@users.noreply.github.com": "davidcampbelldc",
    +    "[redacted:email]": "hehehe0803",  # PR #26212 salvage (codex kanban writable root)
    +    "26063003+hehehe0803@users.noreply.github.com": "hehehe0803",
     }
     
     
    ```
  - `tests/agent/transports/test_codex_app_server_runtime.py`

    ```diff
    @@ -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`

    ```diff
    @@ -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 · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `website/docs/user-guide/features/codex-app-server-runtime.md`
- **#26212: fix(codex): allow sandboxed Kanban worker handoffs**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `4a7cd2e16`, `#27557`
- **#34637: fix(agent): grant Kanban workspace to Codex sandbox**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`
  - files: `agent/transports/codex_app_server.py`, `tests/agent/transports/test_codex_kanban_writable_roots.py`
- **#41905: fix(agent): resume codex app-server thread across turns to preserve session context**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `b5fcf635dc22`, `85cb1dc74b30`, `35cea9b87e48`, `#116343`, `#115759`, `#116586`

## Fix status

- Merged to main in PR #28435 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-05-19`.
- Subsystem: `agent/transports`
