Skip to content

fix(sites): don't list the snippet dir in ReadWritePaths when web.enable is off - #198

Merged
lionello merged 3 commits into
masterfrom
fix/sites-rwp-web-disabled
Aug 10, 2026
Merged

fix(sites): don't list the snippet dir in ReadWritePaths when web.enable is off#198
lionello merged 3 commits into
masterfrom
fix/sites-rwp-web-disabled

Conversation

@defangdevs

Copy link
Copy Markdown
Owner

Master is red and, more importantly, a default (web-less) agent-box no longer starts any agent sessions.

What broke

7b687b9 (the fix for #195, ~/sites being read-only in the agent's own namespace) added /var/lib/agent-box-sites/<user> to the agent unit's ReadWritePaths unconditionally. The tmpfiles rule that creates that directory lives inside mkIf (cfg.enable && cfg.web.enable), and web.enable is a mkEnableOption — default off. With web off the path never exists, and ProtectSystem=strict namespace setup fails hard:

agent-box-agent.service: Failed to set up mount namespacing:
  /var/lib/agent-box-sites/agent: No such file or directory
agent-box-agent.service: Failed at step NAMESPACE ... status=226/NAMESPACE

The unit never starts — no tmux sessions at all — and restart-loops (the counter passed 427 in the CI run). tests/memory-protection.nix, the only test that runs with web off, timed out on wait_for_unit: https://github.com/defangdevs/agent-box/actions/runs/31016454644

The parent commit's reasoning ("tmpfiles-created at sysinit, long before this unit, so no - prefix") is right about ordering — it just doesn't hold when the rule isn't emitted at all.

The fix

Guard the entry on cfg.web.enable, mirroring what gates the tmpfiles rule.

Deliberately still no - prefix: a directory that is genuinely missing while web is on should fail loudly rather than silently reverting to the EROFS that #195 was about.

Verification

Both node configs evaluated directly, no VM boot needed:

config effective ReadWritePaths
web off (memory-protection) ["/home/agent"]
web on (self-serve-domain) ["/home/agent" "/var/lib/agent-box-sites/agent"]

So the #195 flow keeps working where it applies, and the web-off box boots again.

memory-protection now asserts the entry is absent, so the web-off case has an explicit guard instead of only failing indirectly via wait_for_unit.

Also run natively on aarch64: module-generated-up-to-date (regenerated modules/agent-box.nix from .nix.in), the ty/ruff testScript gate, and a drvPath eval of all seven VM tests.

Note: the deploy-test leg is expected-red on master for unrelated reasons (#186/#190).

Opened from a webhook triage session that picked up the #195 close event.

🤖 Generated with Claude Code

https://claude.ai/code/session_014Kq2nqcWR5Y6aryf8WzWbs

7b687b9 added /var/lib/agent-box-sites/<user> to the agent unit's
ReadWritePaths unconditionally, but the tmpfiles rule that creates that
directory lives inside `mkIf (cfg.enable && cfg.web.enable)`. web.enable
is a mkEnableOption, so on a default box the path never exists and
systemd fails the whole namespace setup:

    agent-box-agent.service: Failed to set up mount namespacing:
      /var/lib/agent-box-sites/agent: No such file or directory
    agent-box-agent.service: Failed at step NAMESPACE ... 226/NAMESPACE

The unit then never starts at all — no tmux sessions on any web-less
deployment — and it restart-loops (counter passed 427 in CI). This broke
tests/memory-protection.nix on master, the one test that runs with web
off, which is how it surfaced.

Guard the entry on cfg.web.enable, matching what gates the tmpfiles
rule. Deliberately still no `-` prefix: a dir that is genuinely missing
while web IS on should stay loud rather than silently reverting to the
EROFS the parent commit fixed.

memory-protection now asserts the entry is absent, so the web-off case
has an explicit guard instead of only failing via wait_for_unit.

Verified by evaluating both node configs:
  web off -> ["/home/agent"]
  web on  -> ["/home/agent" "/var/lib/agent-box-sites/agent"]

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Kq2nqcWR5Y6aryf8WzWbs
lionello and others added 2 commits August 6, 2026 10:19
…essions

The standing watch on this box put four hook-* sessions on defangdevs/agent-box
this afternoon for a repo with nothing wrong: merging #189 emitted a green
check_run.completed per job, a green workflow_run, a Pages deployment, and a run
the concurrency group cancelled. Each session read CI, concluded "nothing to do",
and held one of the four hook-session slots that a real failure needs.

0.10.0 was supposed to have stopped that — its own tool description, INSTRUCTIONS
and the agent-box-webhook --deliver-to help all say a CI event spawns only on a
FAILURE. The implementation only used the outcome to decide whether a CI event
could override ignoreSenders, so with the sender not on the ignore list nothing
was gated. An ignore list names the box's own identity (`defangdevs`), while CI
is triggered by whoever pushed (`lionello`), so on this box the brake was never
reached. local-channels 0.10.1 moves the verdict into dispatch_event: a CI
outcome that is not a terminal failure never spawns, whoever triggered it. A
failure still overrides an ignored sender, and session delivery is untouched.

- `webhook.rev` / `webhook.sha256` → local-channels aec22b5 (0.10.1), and
  `modules/agent-box.nix` regenerated from the `.in` source.
- `tests/webhook.nix` gains a leg for it, placed where neither other brake can
  explain the result: the owning session has already handed the topic back and
  the sender is on no ignore list, so only the outcome can hold the spawn. It
  asserts the suppression log line and that no hook-* session appears — the leg
  fails against a pre-0.10.1 pin, which is what makes it a pin test.

Not covered here: the session plugin cache still carries its own copy of
webhook.py (#193), so a session's MCP peer keeps running 0.10.0 until that is
bumped too. Only the dispatcher's copy decides spawns, so the box gets the fix
from this pin alone.

## Verification

- `nix run .#assemble` regenerated the module; `nix build` of the five
  aarch64-evaluable checks (module-generated-up-to-date, module-single-file,
  webhook-route, download-route, multi-user) passes natively.
- `nix eval .#checks.x86_64-linux.webhook.drvPath` resolves, so the new test
  script is valid Nix, and `check-testscript.sh tests/webhook.nix` passes both
  the driver's ty and ruff gates.
- Upstream fix landed as defangdevs/local-channels#12 with 60 unit + e2e tests
  green on python 3.9 and 3.x, mutation-checked.

Claude-Session: https://claude.ai/code/session_0195u4LJGkuYGkxCQQYEXEK1

Co-authored-by: defangdevs <defangdevs@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
chore(webhook): pin local-webhook 0.10.1 so green CI stops spawning sessions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants