feat(webhook): standing watches spawn fresh hook-* sessions (local-webhook 0.9.0) - #164
Merged
Merged
Conversation
…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
This was referenced Aug 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
b80b7a2(local-webhook 0.9.0, new fetchurl hash).agent-box-webhook-spawnwrapper, set asLOCAL_WEBHOOK_SPAWN_CMDon the per-user receiver daemon: event batch on stdin →hook-<key>-<rand>session via the sessions file (same no-sudo runtime path asagent-box-session add; the supervisor starts it within ~2s). Prompt = trusted preamble (which watch fired + cleanup dutyagent-box-session rm NAME) above the per-line[UNTRUSTED webhook:…]event text.AGENT_BOX_HOOK_SESSION_MAX(4) livehook-*sessions, so a watched repo can't slowly fill the box with idle sessions.--ttl 0session pins.Security effects
agent-box-session addsession — no new privilege.Checks run
tests/webhook.nixextended:--deliver-to subagentsubscribe (shared dispatch file,ttlHours 0, daemon env +receiver.jsonadvertisement), signed delivery → exactly onehook-defangdevs-agent-box-*session in sessions.json with preamble + framed event ininitialPrompt, started as a real tmux session; peer session-filter independence.module-generated-up-to-date,multi-user,module-single-file,webhook-route,download-route;nix run .#assemblecommitted.$HOMEwith the realagent-box-session: correct name/prompt/skipPermissions; cap refusal (exit 1) at 4.webhook) runs in CI on x86.No AWS cost / IAM / networking / migration impact.
🤖 Generated with Claude Code
https://claude.ai/code/session_013VoCn5tqBY3hKKwmtCH1kx