Skip to content

fix(server): thread reads can take the latest items and waits return on approvals - #177

Merged
jayleaton merged 2 commits into
mainfrom
fix/thread-read-tail-and-approval-wait
Oct 31, 2026
Merged

jayleaton merged 2 commits into
mainfrom
fix/thread-read-tail-and-approval-wait

Conversation

@jayleaton

Copy link
Copy Markdown
Owner

Two bugs in the thread MCP tools that Captain's listeners hit.

Tool Before After
t3_thread_read limit:1 returns the first item (the opening brief) tail:true returns the last limit items, in position order
t3_thread_wait returnOnQuestion returns only on a live question; approvals need a separate t3_get_pending_questions poll also returns on a live approval and reports pendingApproval: {requestId, kind}

How

  • Read: ProjectionTimelinePageOptions.tail slices the end of the matching index rather than the start, so only the returned items' payloads are decoded. The lightweight index (ids/types, no payloads) is still read in full, as forward reads already do. Positions depend on fork merging and visibility across the whole index, so LIMIT can't move into SQL without breaking them. afterPosition still applies (it gives the latest N after X). nextPosition is still the last returned position. With tail, hasMore means older items exist. Gateway reads (remoteThreads.ts) support it too, and linked environments forward it unchanged.
  • Wait: returnOnQuestion (default true) now covers both kinds of person-blocking request, rather than adding a second option. The wait already subscribes to runtime-request.updated, so an approval that becomes pending wakes it at once. It reports only and never approves. Like delegate_task, only live approvals count, and the oldest is reported.

Not changed: without runId the wait follows the run that was latest at call time and returns when it ends. A run that starts later on the thread (a background continuation) is not followed unless untilDelegatedWorkSettled is set.

Evidence

Each new assertion fails on main:

  • ProjectionStore.test.ts › "keeps fork visible items stable…": expected [ { position: +0, … } ] to deeply equal [ … ] (the tail read got the first item)
  • ThreadManagementService.test.ts › "waitForThread returns early…": hung until the project timeout (120 s), because an approval never ended the wait

With the fix, ThreadManagementService, ProjectionStore, McpHttpServer and OrchestratorMcpService pass (128/128). Server, contracts, client-runtime and evals typecheck clean, and lint and format are clean.

Merge risk

Low. All new fields are optional. A default t3_thread_wait now also returns early when the thread is waiting on an approval. That was intended: the thread is blocked on a person in both cases. Internal waits (settleAfterRun, remote delegation with returnOnQuestion:false) are unaffected. Tool descriptions changed; the mcp-tool-use evals were not run because they are paid.

Claude Opus 5.5 via Claude Code.

🤖 Generated with Claude Code

…on approvals

t3_thread_read with limit:1 returned the first item, so listeners got a
child's opening brief instead of its final reply. A new tail option takes
the last `limit` matching items, still in position order; only those
payloads are decoded. hasMore then means older items exist.

t3_thread_wait with returnOnQuestion only returned on live questions, so
callers polled t3_get_pending_questions for approvals. returnOnQuestion now
also returns on a live approval and reports the oldest as pendingApproval
({requestId, kind}). It reports only; it never approves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M labels Oct 31, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jayleaton jayleaton added the agent-context-increase Approves a measured increase to the agent context budget label Oct 31, 2026
@github-actions

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 5.0 KiB 5.0 KiB −35 B (−0.7%) 6.8 KiB ✅
Codex Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Codex Live turn WebSocket wire 1.3 KiB 1.2 KiB −35 B (−2.7%) 2.0 KiB ✅
Codex Live turn WebSocket decoded 21.6 KiB 21.5 KiB −41 B (−0.2%) 24.4 KiB ✅
Codex Live turn messages 2 1 −1 (−50.0%) 8 ✅
Claude Total thread wire 5.0 KiB 5.0 KiB 0 B (0.0%) 6.8 KiB ✅
Claude Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Claude Live turn WebSocket wire 1.3 KiB 1.3 KiB 0 B (0.0%) 2.0 KiB ✅
Claude Live turn WebSocket decoded 21.9 KiB 21.9 KiB 0 B (0.0%) 24.4 KiB ✅
Claude Live turn messages 2 2 0 (0.0%) 8 ✅

Baseline: e2d81ca · PR result: f6cf9df · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@jayleaton
jayleaton marked this pull request as ready for review October 31, 2026 03:13
@jayleaton
jayleaton merged commit 6357618 into main Oct 31, 2026
32 of 33 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-context-increase Approves a measured increase to the agent context budget size:M vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant