From ac5c10d0beff4a436691dde010e9e88f24a5a68b Mon Sep 17 00:00:00 2001 From: Dai Ha Date: Sun, 16 Aug 2026 17:06:44 +0200 Subject: [PATCH] CB-595: catalogue the async-ticket nudge and the member token shadow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two of the ~14 operator-facing tickets that shipped since v1.0.0 with no Features entry. Written from behaviour I verified directly this session, not from commit messages. CB-588 — a wait:false ticket now nudges the lead's pane when it goes terminal. The why line is the point: the charter tells leads to prefer wait:false, and until this landed that was the one mode with no notification at all. Its gotcha is one I hit myself today. The nudge goes to the lead's PANE, so a lead driving the REST surface gets nothing, and the 10-minute terminal-ticket TTL then prunes the report. A ticket 404'd while its member still sat in done. The work survived only because the implementer skill had opened a PR. The ticket is not the durable artefact; the PR is. CB-592 — the admin GITEA_ACCESS_TOKEN is shadowed in every member's environment, with the BRIDGED_MEMBER marker to win against the pane's login shell re-exporting it. Its gotcha names the limit honestly: this blocks ONE name out of about thirty the login shell sources, and a second forge token is among the unblocked ones. Filed as CB-596. Records why it went unnoticed — present-and-useless looks identical to absent. --- 11-Features.md | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/11-Features.md b/11-Features.md index 3716848..c1fb32b 100644 --- a/11-Features.md +++ b/11-Features.md @@ -1133,6 +1133,59 @@ you rather than forcing it. --- +## Get told when an async delegation finishes + +**What.** A `bridge_send{wait:false}` ticket that reaches a terminal phase — replied, failed, wedged, +or abandoned — now injects a short nudge into the **lead's own pane** telling it to poll. Several +tickets finishing at once coalesce into one nudge naming the count. Polling a ticket marks it +collected, so a ticket you already read is never nudged about again. + +**On.** Automatic when the lead is a herdr pane. Bounded by `fleet.leaders..push_reminders` +(default 5) and `push_backoff_ms` (default 15000). A lead that is not a herdr pane leaves the +registry empty, the loop becomes a no-op, and delivery degrades to pull — nothing is lost. + +**Why.** The charter tells leads to prefer `wait:false` for anything non-trivial, because a blocking +`bridge_send` is capped by the caller's own MCP client timeout of about 60 seconds. But until this +landed, that preferred mode was the one mode with **no notification at all**: `MessageService.reply` +returns on the rendezvous fast path before the push loop hears anything, so an async ticket finished +in silence and the lead only found out by polling on a hunch. + +**Gotcha.** The nudge goes to the lead's **pane**, not to its MCP session. A lead driving the REST +surface directly — which is exactly what you fall back to when the MCP mount drops — receives nothing. +Combine that with the 10-minute terminal-ticket TTL (`MessageService.TICKET_TTL_NANOS`) and a finished +worker's report can be pruned before it is ever read. That happened during this feature's own +close-out: a ticket returned 404 while its member still sat in `done`. The work survived only because +the implementer skill had opened a PR, and the PR body carried the report. **The ticket is not the +durable artefact; the PR is.** If you orchestrate over REST, poll on a timer. + +--- + +## Members cannot use the operator's admin forge token + +**What.** Every member launch overwrites `GITEA_ACCESS_TOKEN` with a non-blank blocked sentinel, and +sets a `BRIDGED_MEMBER=1` marker. Applied in `HerdrPeerLauncher.baseEnv` *after* the profile's own +`env:` map, so no profile — present or future — can name that key and restore the real value. CB-302's +separate `GITEA_TOKEN` grant is untouched, so a member can still push and open its own PR with a +repo-scoped token. + +**On.** Automatic, both adapters, every profile. + +**Why.** A member is an autonomous agent running arbitrary tool calls. It has no business holding the +operator's admin credential, and the rule the operator set is explicit: leads and architects may use +`GITEA_ACCESS_TOKEN`, members use `WORKER_GITEA_TOKEN`. A plain env overlay was not enough — the +pane's login shell re-sources the secret store afterwards and would put the real value back — which is +why the marker exists: the shell's export is guarded on `BRIDGED_MEMBER` being unset. + +**Gotcha, and it is the important one.** This blocks **one name**. The pane's login shell sources a +secret store exporting about **thirty**, and a second forge token is among the ones nothing blocks. +Tracked as **CB-596**. The reason nobody noticed is worth internalising: a Claude Code member inherits +the operator's user-scope `~/.claude.json` servers, so it mounts 45 `mcp__gitea__*` tools including +`delete_branch` and `delete_file`. They all fail — but **only** because the credential they read is +blocked. Remove the block and the same picture becomes 45 working destructive tools, with no visible +difference beforehand. **Present-and-useless looks identical to absent.** + +--- + ## Backfill status This page was started after the fact, so it is **not yet complete**. Entries above are written from