fix: make multi-writer artifacts readable, and stop destroying codex error causes - #270
Merged
Merged
Conversation
…error causes
Both found by the first live end-to-end run of a collaborative session with
real models — a mixed fleet (claude opus 4.8 / sonnet 5, codex gpt-5.6-sol)
where an orchestrator drove a cross-vendor panel. Neither was reachable from
the test suite as written.
## blackboard_read could not address a multi-writer artifact
`findings` is a MULTI_WRITER kind: each reviewer writes its own slot. So
`blackboard_index` correctly reported `findings` in slot `review` — and
`blackboard_read` had no `slot` parameter, read the singleton slot, and
answered "No findings has been written on this goal yet."
Live consequence: the orchestrator saw the artifact in the index, tried to
read it six times, was told it did not exist, concluded "Blackboard still
unreadable on a third attempt", and abandoned the board — routing around
the entire typed-handoff mechanism the feature exists for.
The index advertised something the reader could not fetch.
`read` now takes an optional `slot`, and a slotless read that finds nothing
POINTS AT the slots that do exist instead of claiming the artifact is
absent. The asymmetry with `write` is deliberate and unchanged: §4 gives
`write` no slot so a writer cannot name a peer's and overwrite it. The
service always supported `read(kind, slot)`; only the tool schema was
missing it.
### A test was enforcing the bug
`blackboard-mcp.test.ts` asserted that NO tool accepts a `slot` — stricter
than the design, which forbids it on `write` only. That over-assertion is
why reads were slotless, and it would have rejected this fix. It now states
the invariant per tool: no tool takes a goal or session id (scope rides the
token), `write` never takes a slot, `read` does.
## codex errors arrived as "[object Object]"
The `error` notification carries an object, and the handler did
`(params.message ?? params.error) as string` then `String(...)` — so every
codex failure reported the literal `[object Object]`. The cause was
destroyed at the one place whose job was to report it.
Live consequence: a codex role-child failed twice with `Error: [object
Object]`. Nothing in the transcript, the audit log, or the daemon log said
why, and the orchestrator could only report "identical bare `worker turn
ended in error`, no diagnostic detail".
`describeCodexError` now handles a direct string, a bare string error, a
JSON-RPC-shaped `{code, message}`, and falls back to bounded JSON — chosen
to tolerate shape rather than assert one, because a wrong assumption here
costs the whole diagnostic, which is exactly what happened.
With it, the same run reported:
codex: {"type":"error","status":400,"error":{"type":"invalid_request_error",
"message":"The 'gpt-5.6-sol' model requires a newer version of Codex.
Please upgrade to the latest app or CLI and try again."}}
The codex failure itself is environmental — the installed CLI (client
0.145.0) predates the model, which its own cache still advertises. Not a
codeoid bug, and now diagnosable in one read instead of not at all.
## Verification
Daemon suite 2172 to 2191. Both fixes mutation-checked: removing `slot`
from the read schema fails the multi-writer read test, and reverting the
error extraction fails three of the five codex cases.
The codex error path had NO test before this — which is how `[object
Object]` shipped. It has five now, including one that asserts the string
`[object Object]` never appears for any input shape.
## What the live run confirmed working
Not part of this fix, but it is the first time any of it ran with real
models, so worth recording:
- an orchestrator on opus chose `fleet_panel` itself and fanned one brief to
two children on different vendors
- the R3 gate fired per dispatch with the cost roll-up attached, and the
roll-up accumulated across dispatches ($0 to $0.68 to $1.39)
- the barrier behaved exactly as designed, twice:
`group_waiting done=1/2` then `group_joined members=2 failed=1` — it
absorbed the first completion, did not fire early, and joined on
all-terminal with the failed member reported rather than hanging on it
- after the slot fix, the reviewer's `findings` artifact read back through
the orchestrator's mount, closing the write-then-read handoff
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
abhijitjavelin
approved these changes
Jul 29, 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.
Two defects found by the first live end-to-end run of a collaborative session with real models: a mixed fleet (claude
opus 4.8/sonnet 5, codexgpt-5.6-sol) where an orchestrator drove a cross-vendor panel.Neither was reachable from the test suite as written. One of them a test was actively enforcing.
1.
blackboard_readcould not address a multi-writer artifactfindingsis aMULTI_WRITERkind — each reviewer writes its own slot. Soblackboard_indexcorrectly reportedfindingsin slotreview, andblackboard_readhad noslotparameter, read the singleton slot, and answered "No findings has been written on this goal yet."The index advertised something the reader could not fetch.
Live: the orchestrator saw the artifact, tried six times, was told it didn't exist, concluded "Blackboard still unreadable on a third attempt", and abandoned the board — routing around the entire typed-handoff mechanism the feature exists for.
readnow takes an optionalslot, and a slotless read that finds nothing points at the slots that do exist rather than claiming absence. The asymmetry withwriteis unchanged and deliberate: §4 giveswriteno slot so a writer cannot name a peer's and overwrite it. The service always supportedread(kind, slot)— only the tool schema was missing it.A test was enforcing the bug
blackboard-mcp.test.tsasserted that no tool accepts aslot— stricter than the design, which forbids it onwriteonly. That over-assertion is why reads were slotless, and it would have rejected this fix. It now states the invariant per tool: no tool takes a goal/session id (scope rides the token),writenever takes a slot,readdoes.2. Codex errors arrived as
[object Object]The
errornotification carries an object; the handler did(params.message ?? params.error) as stringthenString(...). Every codex failure reported the literal[object Object]— the cause destroyed at the one place whose job was to report it.Live: a codex role-child failed twice. Nothing in the transcript, audit log, or daemon log said why, and the orchestrator could only report "identical bare
worker turn ended in error, no diagnostic detail."describeCodexErrornow handles a direct string, a bare string error, a JSON-RPC-shaped{code, message}, and falls back to bounded JSON — tolerating shape rather than asserting one, since a wrong assumption costs the whole diagnostic, which is what happened.With it, the same run reported:
The codex failure itself is environmental — the installed CLI (client
0.145.0) predates the model its own cache advertises. Not a codeoid bug, and now diagnosable in one read instead of not at all.Verification
Daemon suite 2172 → 2191. Both fixes mutation-checked: removing
slotfrom the read schema fails the multi-writer read test; reverting the error extraction fails three of the five codex cases.The codex error path had no test at all before this — which is how
[object Object]shipped. It has five now, including one asserting that string never appears for any input shape.What the live run confirmed working
Not part of this fix, but it's the first time any of it ran against real models:
fleet_panelitself and fanned one brief to two children on different vendors$0→$0.68→$1.39group_waiting done=1/2thengroup_joined members=2 failed=1: it absorbed the first completion, did not fire early, and joined on all-terminal with the failed member reported rather than hanging on itfindingsread back through the orchestrator's mount, closing the write-then-read handoff🤖 Generated with Claude Code