Skip to content

[Bug]: bindToCurrentThread is described to agents as always defaulting to true #16185

Description

@coygeek

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

  1. 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."
  2. From an agent thread in project A, call schedule_task with projectId set to project B and no bindToCurrentThread.
  3. 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

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Activity

  1. juliusmarminge commented on Oct 5, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Triage

    Thanks for the careful write-up, @coygeek, and for tracing every place the old wording still shows up!

    What I found

    On current main (f32c23cf), the server behavior matches the schedule_task tool description. scheduleTask in apps/server/src/mcp/OrchestratorMcpService.ts uses input.bindToCurrentThread ?? (parent !== undefined && parent.thread.projectId === projectId). So an omitted field binds only when a thread caller schedules into its own project (including when projectId is omitted). Another projectId, or a caller that isn't a T3 thread, gets threadId: null and a fresh worktree per run, and an explicit true in those cases is invalid_request.

    What agents still see doesn't match that:

    • packages/contracts/src/orchestratorMcp.ts annotates bindToCurrentThread as "True (default) posts each run into this thread…", and the JSDoc above it says the same. Annotated field descriptions are published through Tool.getJsonSchema, and McpServer.toolkit(OrchestratorToolkit) is what tools/list serves.
    • T3_CODE_ORCHESTRATION_INSTRUCTIONS (apps/server/src/provider/T3OrchestrationInstructions.ts) still says "By default runs return to the current thread". That text reaches Claude, Codex, Pi, OpenCode, and ACP sessions when the T3 MCP server is attached.

    The tool description in apps/server/src/mcp/toolkits/orchestrator/tools.ts already states the conditional default correctly.

    This is separate from #16183, which is about those fresh-thread runs hard-coding baseRef: "main". Open #15913 (telemetry records input.bindToCurrentThread !== false) and #15699 (docs say runs post to the calling thread by default) carry the same older assumption.

    Likely fix area

    A maintainer will decide on the fix direction.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions