Repository navigation
Open
Description
Before submitting
- I searched existing issues and did not find a duplicate. Related: fix(server): name the taskIds key in t3_background_wait guidance #31914 (the
taskIdskey in the guidance), fix(server): explain background wait registration timeouts #33658 (registration timeouts).
Area
apps/server
Summary
t3_background_wait refuses calls in ways the calling agent can't repair: it is listed to providers that can never serve it, a task that finished before registration looks like an invented ID, and a miss doesn't say which IDs are running. The refusals themselves are right (nothing is registered wrongly); the agent just gets nothing to retry with. We have been running the fix below in a fork and are happy to send a PR.
What happens
- Same-response registration. Claude often sends
Bash run_in_backgroundand
t3_background_waitin one assistant message. The task ID exists only in the Bash result, so the
wait carries an ID the model made up. It is refused as unmatched, and the model usually retries a
few seconds later with the real ID. The instruction text ("callt3_background_waitwith its task
ID before you end your turn") does not say when the ID exists. One model also sent
{"taskId": "…"}(singular). - Finished before registration. A short command can end, with its
task_notificationhandled
and the roster entry gone, before the model's next response registers it. That is refused exactly
like an invented ID. - Unsupported providers.
BackgroundWaitToolhas noMcpSchema.EnabledWhen, so Codex, Pi, ACP
agents and others list a tool that always fails with "This provider cannot mark background work as
awaited." The handler maps that toinvalid_request, so it reads as the agent's mistake.
Where
apps/server/src/mcp/toolkits/orchestrator/tools.ts:BackgroundWaitTool(description, no
EnabledWhen).apps/server/src/orchestration-v2/ThreadManagementService.ts:awaitBackgroundTasks;
ThreadManagementBackgroundWaitErrorunmatchedmessage.packages/provider-core/src/server/ProviderAdapter.ts:awaitBackgroundTasksreturns only
{ matched }.apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts: matches the pending roster only;
CLAUDE_BACKGROUND_WAIT_INSTRUCTIONS.packages/provider-opencode/src/server/v2/adapter.ts: matches running shells only; the shell
instruction text.
Proposed fix
- List the tool only where it works. Add an adapter-level capability (we used
backgroundWait?: Effect.Effect<boolean>on theProviderAdapterV2service, so the composite
OpenCode driver can answer for the protocol version a session would use). Stamp it on the MCP
thread credential when it is minted, next tosubagent, and annotateBackgroundWaitToolwith an
EnabledWhenthat reads it. Keep theunsupportedbranch as a defence. - Report finished tasks as settled. Each adapter remembers task IDs that finished on a thread
(bounded; we keep 64 per session).awaitBackgroundTasksreturns
{ matched, settled?, running? }. A registration whose IDs are only settled succeeds with
settled: [...]("already finished: read their output now") and holds nothing. A reused ID that
is running again counts as running. - Name the running tasks on a miss. When nothing matched and nothing settled, return up to 5
running, not-yet-awaited tasks with a bounded description (the command or task description). The
unmatched message appends: "Running and not yet awaited here:<id>(). Call again
with one of these IDs." The agent started those commands itself, so nothing new is disclosed. - Say when the ID exists. Tool description and both instruction texts: "In a later response
than the start, pass the ID its result shows:taskIds: [id]." (fix(server): name the taskIds key in t3_background_wait guidance #31914 already fixes the
taskIdskey wording; this adds the "later response" part.)
Not proposed: waiting server-side for an unknown ID to appear (an invented ID never appears),
treating every unmatched ID as success, or accepting taskId as an alias.
Tests we added
- An invented ID while two tasks run returns
matched: []and both asrunning; an awaited task is
no longer listed; after itstask_notification, registering it returnssettled: [id](Claude
adapter, driven by SDK frames). - The OpenCode shell tests that registered a finished shell now expect
settled: [shellId]. - The thread service passes
settledthrough as success and appends the running list to the
unmatched error. EnabledWhenlists the tool for a credential with the capability and hides it otherwise.
Metadata
Metadata
Assignees
Labels
No labels
Activity