CB-595: catalogue the async-ticket nudge and the member token shadow

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.
Dai Ha
2026-08-16 17:06:44 +02:00
parent 02d2f096cf
commit ac5c10d0be
+53
@@ -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.<name>.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