From 2e45c2098895d067dbf7cc44c9c90e91d28a3520 Mon Sep 17 00:00:00 2001 From: Lio Lunesu Date: Wed, 5 Aug 2026 14:57:42 +0000 Subject: [PATCH] chore(webhook): pin local-webhook 0.10.1 so green CI stops spawning sessions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Co-authored-by: Claude Opus 5 --- modules/agent-box.nix | 4 ++-- modules/agent-box.nix.in | 4 ++-- tests/webhook.nix | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/modules/agent-box.nix b/modules/agent-box.nix index afa0dd5..d38a8c0 100644 --- a/modules/agent-box.nix +++ b/modules/agent-box.nix @@ -2103,7 +2103,7 @@ in }; rev = lib.mkOption { type = lib.types.str; - default = "efa253166074789ece9709e4a1e3a4be58801166"; + default = "aec22b5878b19dca12de5c954e86302cd6c7bdd8"; description = '' Pinned local-channels commit whose local-webhook/webhook.py the receiver daemon and the agent-box-webhook CLI run. Claude sessions @@ -2117,7 +2117,7 @@ in # builtins.fetchurl hash of local-webhook/webhook.py at `rev`: # nix-prefetch-url https://raw.githubusercontent.com///local-webhook/webhook.py # then `nix hash convert --hash-algo sha256 --to sri `. - default = "sha256-qHVF398YAwiIqbYG2/HY+hVzY4HF4saiasdZHAXngIM="; + default = "sha256-fZKy5jCsn3bom9F4E3dqW7N7x9ky+IM4NXFzLLSBOtU="; description = "builtins.fetchurl hash of the pinned local-webhook/webhook.py."; }; }; diff --git a/modules/agent-box.nix.in b/modules/agent-box.nix.in index 65e6842..751ee4a 100644 --- a/modules/agent-box.nix.in +++ b/modules/agent-box.nix.in @@ -2099,7 +2099,7 @@ in }; rev = lib.mkOption { type = lib.types.str; - default = "efa253166074789ece9709e4a1e3a4be58801166"; + default = "aec22b5878b19dca12de5c954e86302cd6c7bdd8"; description = '' Pinned local-channels commit whose local-webhook/webhook.py the receiver daemon and the agent-box-webhook CLI run. Claude sessions @@ -2113,7 +2113,7 @@ in # builtins.fetchurl hash of local-webhook/webhook.py at `rev`: # nix-prefetch-url https://raw.githubusercontent.com///local-webhook/webhook.py # then `nix hash convert --hash-algo sha256 --to sri `. - default = "sha256-qHVF398YAwiIqbYG2/HY+hVzY4HF4saiasdZHAXngIM="; + default = "sha256-fZKy5jCsn3bom9F4E3dqW7N7x9ky+IM4NXFzLLSBOtU="; description = "builtins.fetchurl hash of the pinned local-webhook/webhook.py."; }; }; diff --git a/tests/webhook.nix b/tests/webhook.nix index 248e2b3..60d6728 100644 --- a/tests/webhook.nix +++ b/tests/webhook.nix @@ -363,6 +363,40 @@ " agent-box-webhook unsubscribe defangdevs/agent-box" ) + # --- dispatch brake: a green run is not news (local-channels 0.10.1) ----- + # Nobody owns the topic now and this sender is on no ignore list, so the + # OUTCOME is the only thing that can hold the spawn back. Pinned webhook.py + # before 0.10.1 read the outcome only to decide whether a CI event could + # override an ignored sender, and started a session per green build: one + # merge to master cost four hook-* sessions that each concluded "nothing to + # do", with the four-session cap then standing between a real failure and + # its triage. + client.succeed( + "cat > /tmp/green.json <<'EOF'\n" + '{"action":"completed","workflow_run":{"name":"CI","conclusion":"success",' + '"head_branch":"master","html_url":"https://box.test/run/2"},' + '"repository":{"full_name":"defangdevs/agent-box"},"sender":{"login":"someone"}}\n' + "EOF" + ) + sig_green = client.succeed( + f"openssl dgst -sha256 -hmac {secret} -r /tmp/green.json | cut -d' ' -f1" + ).strip() + client.succeed( + f"{curl} -o /dev/null -w '%{{http_code}}' -X POST" + " -H 'content-type: application/json' -H 'x-github-event: workflow_run'" + " -H 'x-github-delivery: test-green'" + f" -H 'x-hub-signature-256: sha256={sig_green}' --data-binary @/tmp/green.json" + " https://box.test/agent/webhook/github | grep -x 200" + ) + machine.wait_until_succeeds( + "journalctl -u agent-box-webhook-agent --no-pager | grep -q 'no failing outcome'", + timeout=30, + ) + machine.fail( + "jq -e '.sessions | keys | map(select(startswith(\"hook-\"))) | length > 0'" + " /home/agent/.config/agent-box/sessions.json" + ) + # A signed delivery on the watched repo → a fresh hook-* session appears in # sessions.json, primed with the framed event text plus the trusted # preamble, and the supervisor starts it as a real tmux session.