Skip to content

feat(webhook): standing watches spawn fresh hook-* sessions (local-webhook 0.9.0) - #164

Merged
defangdevs merged 1 commit into
masterfrom
feat/webhook-dispatch-sessions
Aug 2, 2026
Merged

feat(webhook): standing watches spawn fresh hook-* sessions (local-webhook 0.9.0)#164
defangdevs merged 1 commit into
masterfrom
feat/webhook-dispatch-sessions

Conversation

@defangdevs

Copy link
Copy Markdown
Owner

Companion to defangdevs/local-channels#9 (local-webhook 0.9.0); closes the agent-box side of defangdevs/local-channels#1.

Motivation

Events nobody owns — a new issue, a new PR, CI failing on a repo no session is working on — previously had to ride a pinned per-session subscription, which interrupts whichever session happens to be active, indefinitely. local-webhook 0.9.0 adds deliver_to:"subagent" dispatch: a shared, default-pinned watch list the receiver daemon routes after fan-out, spawning a fresh session per (coalesced) event batch through a configurable spawn command.

What this wires

  • Pin bump to local-channels b80b7a2 (local-webhook 0.9.0, new fetchurl hash).
  • agent-box-webhook-spawn wrapper, set as LOCAL_WEBHOOK_SPAWN_CMD on the per-user receiver daemon: event batch on stdin → hook-<key>-<rand> session via the sessions file (same no-sudo runtime path as agent-box-session add; the supervisor starts it within ~2s). Prompt = trusted preamble (which watch fired + cleanup duty agent-box-session rm NAME) above the per-line [UNTRUSTED webhook:…] event text.
  • Accumulation cap: webhook.py already coalesces bursts and caps concurrent spawns; the wrapper additionally refuses to create more than AGENT_BOX_HOOK_SESSION_MAX (4) live hook-* sessions, so a watched repo can't slowly fill the box with idle sessions.
  • Seeded AGENTS.md + README document the standing-watch shape and stop suggesting --ttl 0 session pins.

Security effects

  • Spawn command receives payload-derived text on stdin only (never argv); session names sanitized to the session-name charset; UNTRUSTED framing preserved under a trusted preamble.
  • Dispatch fails closed at every layer (no spawn cmd / no dispatch file / corrupt file → spawn nothing) — boxes with no standing watch behave exactly as before. Ingress auth/status codes unchanged.
  • The spawned session runs as the same user with the same autonomy as any agent-box-session add session — no new privilege.

Checks run

  • tests/webhook.nix extended: --deliver-to subagent subscribe (shared dispatch file, ttlHours 0, daemon env + receiver.json advertisement), signed delivery → exactly one hook-defangdevs-agent-box-* session in sessions.json with preamble + framed event in initialPrompt, started as a real tmux session; peer session-filter independence.
  • Eval checks green natively on aarch64: module-generated-up-to-date, multi-user, module-single-file, webhook-route, download-route; nix run .#assemble committed.
  • Wrapper smoke-tested against a sandbox $HOME with the real agent-box-session: correct name/prompt/skipPermissions; cap refusal (exit 1) at 4.
  • Full VM test suite (incl. webhook) runs in CI on x86.

No AWS cost / IAM / networking / migration impact.

🤖 Generated with Claude Code

https://claude.ai/code/session_013VoCn5tqBY3hKKwmtCH1kx

…bhook 0.9.0)

Events nobody owns — a new issue, a failing run on a repo no session is
working on — previously had to ride a pinned per-session subscription,
interrupting whatever session happened to be active, indefinitely.
local-channels#1 added deliver_to:"subagent" dispatch to local-webhook
0.9.0: a shared, default-pinned subscription list the ingress daemon
routes after fan-out, spawning a fresh session per (coalesced) event
batch via a configurable spawn command.

This wires it into the box:

- Bump the pinned local-webhook to 0.9.0 (local-channels b80b7a2).
- New agent-box-webhook-spawn wrapper, set as LOCAL_WEBHOOK_SPAWN_CMD on
  the per-user receiver daemon. It turns the event batch on stdin into a
  hook-<key>-<rand> session via the sessions file (the same no-sudo
  runtime path agent-box-session uses; the supervisor starts it within
  ~2s). The prompt = trusted preamble (which watch fired, cleanup duty:
  agent-box-session rm NAME) + the per-line UNTRUSTED-framed event text.
  webhook.py already coalesces bursts and caps concurrent spawns; the
  wrapper additionally refuses to create more than
  AGENT_BOX_HOOK_SESSION_MAX (4) live hook-* sessions, so a watched repo
  cannot slowly fill the box with idle sessions if spawned agents fail
  to clean up after themselves.
- Seeded AGENTS.md + README document the standing-watch shape and stop
  suggesting `--ttl 0` session pins.

Security: the spawn command receives payload-derived text on stdin only
(never argv), session names are sanitized to the session-name charset,
and the payload keeps its per-line [UNTRUSTED webhook:...] framing under
a trusted preamble. Dispatch fails closed at every layer (no spawn cmd /
no dispatch file / corrupt file → spawn nothing), so boxes without a
standing watch behave exactly as before.

Verification:

- tests/webhook.nix extended: subscribes a --deliver-to subagent watch
  (asserts shared dispatch file, ttlHours 0, daemon env + receiver.json
  advertisement), posts a signed delivery, and asserts a single
  hook-defangdevs-agent-box-* session appears in sessions.json with the
  preamble + framed event in initialPrompt, gets started as a real tmux
  session, and that the peer's session filter stayed independent.
- Eval checks pass natively (aarch64): module-generated-up-to-date,
  multi-user, module-single-file, webhook-route, download-route.
- Wrapper logic smoke-tested against a sandbox HOME with the real
  agent-box-session CLI: correct name/prompt/skipPermissions, cap
  refusal at 4 with exit 1.

Companion to defangdevs/local-channels#9. Closes the agent-box side of
local-channels#1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VoCn5tqBY3hKKwmtCH1kx
@defangdevs
defangdevs merged commit d53a908 into master Aug 2, 2026
1 check passed
@defangdevs
defangdevs deleted the feat/webhook-dispatch-sessions branch August 2, 2026 08:40
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