CB-582 + CB-604: the ask nudge, and kind: is now validated
The kind: entry documented the unvalidated-typo gotcha as live. CB-604 fixed it, so the entry now carries the refusal message instead, and points at CB-606 for the three fields that still have the same shape. New entry for CB-582: a worker paused on bridge_ask nudges the lead's pane, and bridge_status and REST both show the open question. The gotcha is the part that matters — the ~55s window is closed, not removed, so 'do not brief a worker to ask me' still stands.
+56
-8
@@ -1166,6 +1166,43 @@ durable artefact; the PR is.** If you orchestrate over REST, poll on a timer.
|
||||
|
||||
---
|
||||
|
||||
## Get told when a worker is waiting on your answer
|
||||
|
||||
**What.** A worker on an async (`wait:false`) delegation that pauses mid-turn in `bridge_ask` now
|
||||
nudges the lead's pane by itself, naming the exact call that resumes it:
|
||||
|
||||
```
|
||||
Worker term_a asked a question (ticket task-3) — answer it with
|
||||
bridge_send(turnId="term_a#1", content=...) to resume its turn:
|
||||
which config file?
|
||||
```
|
||||
|
||||
`bridge_status{sessionId}` shows the same open question, and so does REST
|
||||
`GET /sessions/{id}/status` (as `question`, `turnId`, `ticket`).
|
||||
|
||||
**On.** Automatic, on the same terms as the ticket nudge above — it is a **third source in the same
|
||||
per-lead schedule**, not a new push path, so CB-590's one-schedule-per-lead guarantee still holds and
|
||||
it spends from its own `push_reminders` budget.
|
||||
|
||||
**Why.** `bridge_ask` opens a reverse-rendezvous window of about **55 seconds**. A lead polling on its
|
||||
normal cadence of minutes never saw it, so the worker timed out and carried on without an answer —
|
||||
the ask was, in practice, unusable on the delegation mode the charter tells leads to prefer. Two
|
||||
smaller holes closed with it: REST `GET /tasks/{ticket}` dropped `turnId` on an `ASKING` phase, so a
|
||||
REST caller could read the question and had no way to answer it, and `bridge_status` said nothing
|
||||
about an open question at all.
|
||||
|
||||
**Gotcha.** **This closes the window; it does not remove it.** The worker still gets ~55 seconds, and
|
||||
a nudge only helps a lead that is injectable right now — a lead mid-turn for a minute still misses it.
|
||||
So the standing advice is unchanged: **do not brief a worker to "ask me."** Decide the question before
|
||||
you delegate, or give the worker an explicit default to use.
|
||||
|
||||
Why the window is not simply widened: `DEFAULT_ASK_TIMEOUT_MS = 55_000` sits just under the *worker's
|
||||
own* MCP client cap of about 60 seconds, so the daemon can return a clean typed timeout before the
|
||||
client severs the call. Raising the server constant buys nothing — the worker's client kills the call
|
||||
regardless.
|
||||
|
||||
---
|
||||
|
||||
## Members cannot use the operator's admin forge token
|
||||
|
||||
**What.** Every member launch overwrites `GITEA_ACCESS_TOKEN` with a non-blank blocked sentinel, and
|
||||
@@ -1671,14 +1708,25 @@ adapter.
|
||||
existing *Pin an opencode endpoint* entry). The partition-by-kind design itself reads as the
|
||||
natural consequence: profiles fully own their backend, so routing is a lookup, not a branch.
|
||||
|
||||
**Gotcha.** `kind:` is lower-cased but **never validated against the two known values.** A typo —
|
||||
`kind: opencod`, say — is silently accepted, normalized, and (because it doesn't equal
|
||||
`"opencode"`) routed into the **claude-code** adapter bucket. If `argv:` was also left unset, the
|
||||
launch command defaults to `List.of(k)` — literally the misspelled string itself — rather than
|
||||
`claude`, because the argv-defaulting logic only special-cases the exact string
|
||||
`"claude-code"`. There is no config-load check anywhere that would catch this before spawn.
|
||||
Separately, the composite constructor does refuse two adapters claiming the same profile name
|
||||
("worker profile '…' is claimed by two peer adapters"), so that failure mode is caught loud.
|
||||
**Validated at config load since CB-604 (2026-08-16).** An unrecognized `kind:` now refuses to
|
||||
start, naming the profile, the bad value, the accepted set, and what would otherwise happen:
|
||||
|
||||
```
|
||||
refusing to start: profile(s) [gemini=opencod] set an unrecognized kind — accepted values are
|
||||
claude-code, opencode (case-insensitive); an unrecognized kind would otherwise fall back to the
|
||||
claude-code adapter and try to launch a program named after the typo.
|
||||
```
|
||||
|
||||
Before that fix, `kind: opencod` was silently accepted, normalized, and — because it did not equal
|
||||
`"opencode"` — routed into the **claude-code** adapter bucket. With `argv:` also unset, the launch
|
||||
command defaulted to `List.of(kind)`, literally the misspelled string, because the argv default only
|
||||
special-cases the exact string `"claude-code"`. Nothing caught it before the spawn failed.
|
||||
|
||||
**Gotcha.** The composite constructor separately refuses two adapters claiming the same profile name
|
||||
("worker profile '…' is claimed by two peer adapters"), so that failure mode has always been loud.
|
||||
Checking for the same shape elsewhere found three more unvalidated fields — `auth.mode`, the
|
||||
per-profile `placement:`, and the top-level `placement:` policy, which is validated but only lazily
|
||||
at first spawn. That is CB-606; until it lands, a typo in those three is still accepted silently.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user