Summary
Since #15219 made schedule_task accept a projectId, the server defaults bindToCurrentThread to true only when a thread caller schedules in its own project, and to false otherwise. The field description that agents receive in the tool's JSON input schema still says "True (default) posts each run into this thread", and the injected agent instructions still say "By default runs return to the current thread". An agent that schedules into another project and omits the field is told its runs will come back to its thread, while each run actually launches a fresh top-level thread with its own worktree in the other project. The schedule_task tool description is already correct, so the agent sees two contradicting statements.
Steps to reproduce
- Read the
schedule_task input schema as served to agents, for example via MCP tools/list: the bindToCurrentThread property description is "True (default) posts each run into this thread; false creates a fresh top-level thread per run."
- From an agent thread in project A, call
schedule_task with projectId set to project B and no bindToCurrentThread.
- Call
list_scheduled_tasks with projectId B and read the task's bound thread.
Step 1 was established from source plus the probe: Effect's McpServer builds each tool's inputSchema with Schema.toJsonSchemaDocument, and an isolated effect@4.0.1 probe with the same struct emitted the description on the bindToCurrentThread property; a live tools/list was not captured. Steps 2-3 were not executed.
Expected behavior
The agent-facing field description and injected instructions should state the default the server applies, consistent with the tool description at apps/server/src/mcp/toolkits/orchestrator/tools.ts:100: "In this thread's project, runs post into THIS thread by default (bindToCurrentThread=true) ... Elsewhere each run launches a fresh thread." For step 2, an agent following the field description should be told the task will launch fresh threads in project B.
Actual behavior
The server computes input.bindToCurrentThread ?? (parent !== undefined && parent.thread.projectId === projectId), so step 2 creates an unbound task: threadId: null and a fresh worktree strategy per run, with nothing posted back to the scheduling thread. Passing true explicitly for another project fails with invalid_request ("bindToCurrentThread binds to this thread, which belongs to a different project."). The field description and its JSDoc (both from #2829) and the instructions line were not updated by #15219, which changed the default from an unconditional ?? true.
Evidence
Runtime reproduction was not run. Agent visibility: apps/server/src/mcp/McpHttpServer.ts:663 registers McpServer.toolkit(OrchestratorToolkit); Effect 4.0.1 McpServer.registerToolkit builds inputSchema from the tool parameters schema via Schema.toJsonSchemaDocument; the repo's own toolkits/orchestrator/tools.test.ts already asserts that field descriptions (for example schedule and timeoutMs) appear in the generated schema; and the isolated probe emitted "bindToCurrentThread":{"anyOf":[{"type":"boolean","description":"True (default) posts each run into this thread; false creates a fresh top-level thread per run."},{"type":"null"}]}. The instructions line is included in Claude, Codex, and Pi provider instructions.
Restoration check
Read the generated schedule_task input schema and the injected instructions: today the bindToCurrentThread description says "True (default)" unconditionally and the instructions say runs return to the current thread by default; after the fix both state that the default is true only when scheduling in the calling thread's own project and that other projects get a fresh thread per run. Control: the tool description and the server default stay as they are, and an omitted field in the caller's own project still binds runs to the calling thread.
Additional context
The same assumption appears in open PR #15913 (tool-usage telemetry records bindToCurrentThread: input.bindToCurrentThread !== false for schedule_task), which would count cross-project unbound tasks as bound. Open docs PR #15699 also states "Runs post to the calling thread by default" without the project condition. When MCP callers outside a T3 thread arrive (#15220), "this thread" has no referent for them and their omitted default is false. An unbound task in a project without a main branch also hits the base-branch failure reported in #16183, which compounds the surprise but has its own restoration.
Area
packages/contracts or packages/shared
Impact
Minor bug or occasional failure. Agent-facing wording contradicts the server default for cross-project scheduling; server behavior and the tool description are correct.
Version or commit
main at 6f9cea00ae967f38fa3cdc9c07f92031806f4264.
Environment
Source inspection of pingdotgg/t3code main at 6f9cea0, plus an isolated schema probe with effect@4.0.1. Not executed against a running T3 server.
Workaround
The tool-level description states the correct default, and list_scheduled_tasks shows whether a task is bound, so an agent or user can confirm placement after scheduling.
Before submitting
Summary
Since #15219 made
schedule_taskaccept aprojectId, the server defaultsbindToCurrentThreadto true only when a thread caller schedules in its own project, and to false otherwise. The field description that agents receive in the tool's JSON input schema still says "True (default) posts each run into this thread", and the injected agent instructions still say "By default runs return to the current thread". An agent that schedules into another project and omits the field is told its runs will come back to its thread, while each run actually launches a fresh top-level thread with its own worktree in the other project. Theschedule_tasktool description is already correct, so the agent sees two contradicting statements.Steps to reproduce
schedule_taskinput schema as served to agents, for example via MCPtools/list: thebindToCurrentThreadproperty description is "True (default) posts each run into this thread; false creates a fresh top-level thread per run."schedule_taskwithprojectIdset to project B and nobindToCurrentThread.list_scheduled_taskswithprojectIdB and read the task's bound thread.Step 1 was established from source plus the probe: Effect's
McpServerbuilds each tool'sinputSchemawithSchema.toJsonSchemaDocument, and an isolatedeffect@4.0.1probe with the same struct emitted the description on thebindToCurrentThreadproperty; a livetools/listwas not captured. Steps 2-3 were not executed.Expected behavior
The agent-facing field description and injected instructions should state the default the server applies, consistent with the tool description at
apps/server/src/mcp/toolkits/orchestrator/tools.ts:100: "In this thread's project, runs post into THIS thread by default (bindToCurrentThread=true) ... Elsewhere each run launches a fresh thread." For step 2, an agent following the field description should be told the task will launch fresh threads in project B.Actual behavior
The server computes
input.bindToCurrentThread ?? (parent !== undefined && parent.thread.projectId === projectId), so step 2 creates an unbound task:threadId: nulland a fresh worktree strategy per run, with nothing posted back to the scheduling thread. Passingtrueexplicitly for another project fails withinvalid_request("bindToCurrentThread binds to this thread, which belongs to a different project."). The field description and its JSDoc (both from #2829) and the instructions line were not updated by #15219, which changed the default from an unconditional?? true.Evidence
Runtime reproduction was not run. Agent visibility:
apps/server/src/mcp/McpHttpServer.ts:663registersMcpServer.toolkit(OrchestratorToolkit); Effect 4.0.1McpServer.registerToolkitbuildsinputSchemafrom the tool parameters schema viaSchema.toJsonSchemaDocument; the repo's owntoolkits/orchestrator/tools.test.tsalready asserts that field descriptions (for examplescheduleandtimeoutMs) appear in the generated schema; and the isolated probe emitted"bindToCurrentThread":{"anyOf":[{"type":"boolean","description":"True (default) posts each run into this thread; false creates a fresh top-level thread per run."},{"type":"null"}]}. The instructions line is included in Claude, Codex, and Pi provider instructions.Restoration check
Read the generated
schedule_taskinput schema and the injected instructions: today thebindToCurrentThreaddescription says "True (default)" unconditionally and the instructions say runs return to the current thread by default; after the fix both state that the default is true only when scheduling in the calling thread's own project and that other projects get a fresh thread per run. Control: the tool description and the server default stay as they are, and an omitted field in the caller's own project still binds runs to the calling thread.Additional context
The same assumption appears in open PR #15913 (tool-usage telemetry records
bindToCurrentThread: input.bindToCurrentThread !== falseforschedule_task), which would count cross-project unbound tasks as bound. Open docs PR #15699 also states "Runs post to the calling thread by default" without the project condition. When MCP callers outside a T3 thread arrive (#15220), "this thread" has no referent for them and their omitted default is false. An unbound task in a project without amainbranch also hits the base-branch failure reported in #16183, which compounds the surprise but has its own restoration.Area
packages/contracts or packages/shared
Impact
Minor bug or occasional failure. Agent-facing wording contradicts the server default for cross-project scheduling; server behavior and the tool description are correct.
Version or commit
mainat6f9cea00ae967f38fa3cdc9c07f92031806f4264.Environment
Source inspection of
pingdotgg/t3codemainat 6f9cea0, plus an isolated schema probe witheffect@4.0.1. Not executed against a running T3 server.Workaround
The tool-level description states the correct default, and
list_scheduled_tasksshows whether a task is bound, so an agent or user can confirm placement after scheduling.Before submitting