t3code hands the current folder or Git repository to a new thread in T3 Code, and lets automation discover, inspect, and message existing threads.
It does not fake a handover by copying text or opening a generic app URL. It connects to the running local T3 server, resolves the workspace against T3 projects, optionally creates the missing project, creates a fresh thread, and starts its first prompt through T3's orchestration API.
Requirements: Node.js 22.16+ and T3 Code.
npm install --global @bvdm/t3code-cliThen verify discovery and the active T3 server:
t3code --json doctorRequirements: Node.js 22.16+ and T3 Code.
pnpm install
pnpm check
pnpm build
npm linkThen verify the linked command:
t3code --json doctorFrom any folder in a repository:
t3code handover --prompt "Continue from this handover..."When --cwd is omitted, the command starts from the process's current working directory. In the default repo workspace mode, that folder then resolves to its Git root.
For larger prompts, pass stdin so shell command-length and quoting rules do not matter:
printf '%s' "Continue from this handover..." | t3code handover --stdinPowerShell:
'Continue from this handover...' | t3code handover --stdinThe default behavior is:
- resolve the Git repository root (
workspaceMode: "repo"); a linked worktree, such as one T3 created for another thread, resolves to the main checkout's project, and acurrentcheckout handover keeps the new thread in that worktree; - create a missing T3 project (
projectPolicy: "create"); - resolve T3's checkout preference in the same order as the installed app: project setting, checked-in
t3.json, then the global setting; - inherit an existing project's complete model selection, including its provider options;
- use full access for both the new thread and its first turn;
- create a fresh thread and start the prompt through T3's orchestration commands;
- reveal T3 Code after dispatch.
Use --dry-run --json to inspect the exact project and thread commands without writing T3 state.
Select the new thread's T3 controls on the handover command:
t3code handover \
--provider codex \
--model gpt-6-astra \
--speed fast \
--thinking-effort xhigh \
--permission full-access \
--mode build \
--checkout current \
--prompt "Continue from this handover..."| Control | Values |
|---|---|
--provider |
A configured T3 provider instance id, such as codex or claudeAgent |
--model |
A model slug supported by that provider instance |
--speed, --speed-mode |
standard, fast |
--thinking-effort |
A model-supported value such as low, medium, high, xhigh or max |
--permission, --runtime-mode |
approval-required, auto-accept-edits, full-access |
--mode, --interaction-mode |
build/default, plan |
--checkout, --env-mode |
current/local, worktree, or T3's configured default via t3 |
Command flags override the CLI config, which overrides the T3 project's saved model selection. Without either override, the saved selection and its options are passed through unchanged. A newly-created project uses the detected T3 version's default (gpt-5.4 on 0.0.28 and gpt-5.6-sol on 0.0.29 and later).
Speed and thinking effort are stored as model options. T3 applies the option ids supported by the selected provider/model. If --provider changes the project's default provider instance, also pass --model because provider instance ids can be user-defined and do not imply a model.
List threads across projects, or restrict discovery by project id or workspace:
t3code threads list
t3code threads list --status active --cwd .
t3code threads list --status settled --project <project-id>--status accepts active, settled, or all (the default). Results include the exact thread id, project, title, model, and update time. Inspect the exact target before sending:
t3code threads inspect --thread <thread-id>inspect prints the workspace and branch, model, turn count, latest turn state, context use, and any approval or question the thread waits on. Its JSON also holds a bounded preview: the 6 most recent messages, with message text limited to 2,000 characters.
Read the conversation itself with read. It prints a Markdown transcript grouped by turn, which an agent can read directly:
t3code threads read --thread <thread-id>
t3code threads read --thread <thread-id> --detail answers --turns 3 --first-turn
t3code threads read --thread <thread-id> --detail full --last-turn --max-chars 1500
t3code --json threads read --thread <thread-id>--detail sets how much of each turn to return:
answers: the user's prompts and the turn's final answer.messages(default): prompts and every assistant message, without reasoning summaries or tool calls.full: everything, including reasoning summaries, tool calls, and changed files.
A plan-mode turn's proposed plan is its answer, so it appears at every level.
--turns <n> keeps the last n turns, and --last-turn is short for --turns 1. --first-turn adds the first turn, which holds the original request. --max-chars <n> clips each message and tool entry but keeps its start and end. Without it, message text is never shortened.
T3 stores user messages without a turn id. The CLI assigns each one to the turn it started, so a prompt stays with its answer. A message sent during a running turn stays with that turn, because providers usually fold it in. A provider can also queue it and start a new turn right after; a turn that starts within five seconds of the previous one ending, without a prompt of its own, takes the message. Messages that no turn has picked up yet appear as a pending group.
The JSON result keeps data.thread.messages in turn order and adds turnIndex and textTruncated to each message. data.thread.turns describes each returned turn: its number, state, final message id, and, in full detail, its changed files and tool call count. T3 reports the state of the latest turn only, so earlier turns have a null state. data.thread.view reports the detail level and how many turns were returned or left out. full also returns data.thread.toolCalls and data.thread.proposedPlans. T3 shortens tool output to its first line and keeps at most 500 activities per thread, so very long threads lose their oldest tool calls. Changed files come from T3's checkpoint diff of the workspace, so they include any other edits made there during the turn.
Start a new turn on that thread with one of --prompt, --prompt-file, or --stdin:
printf '%s' "Review findings from the other thread..." \
| t3code threads send --thread <thread-id> --stdinSending to a settled thread requires confirmation. Non-interactive and JSON callers must explicitly opt in with --wake-settled:
printf '%s' "New findings that require more work..." \
| t3code --json threads send --thread <thread-id> --stdin --wake-settledThe send command does not report success from the HTTP response alone. It waits until the exact message is visible in T3's thread projection. Archived threads are rejected.
By default, send refuses a busy thread with THREAD_BUSY and dispatches nothing. A thread is busy while a turn runs or while an earlier message still waits for its turn; error.details says which. Wait with threads wait and send again, or pass --if-busy inject to send into the running turn. The provider then folds the message into that turn or queues it, which --wait follows either way. The busy check is a snapshot, not a lock, so callers that send to the same thread at once must take turns themselves.
Add --wait to wait for the turn that handles the message and print its reply:
printf '%s' "Which tests still fail?" \
| t3code --json threads send --thread <thread-id> --stdin --wait --timeout 540data.wait.outcome is one of:
completedorinterrupted:data.replyholds that turn as a transcript, without your own message.ended: the turn finished, but a later turn replaced it before the wait could see whether it completed.data.replystill holds it.error: the provider could not start the turn;data.wait.errorsays why.needs-attention: the thread waits for an approval or an answer, listed indata.pendingRequests.
The reply uses --detail answers unless you pass another level. A finished turn must show on two consecutive polls, two seconds apart. When your message reached the thread mid-turn, the wait also gives a queued turn five seconds to start, so the running turn's answer is not mistaken for the reply. When the wait times out, the command fails with THREAD_WAIT_TIMEOUT and error.details.sent: true. Do not resend the message; keep waiting with threads wait.
To wait for whatever a thread is doing, for example after a handover:
t3code threads wait --thread <thread-id> --timeout 540Both waits default to 600 seconds. A waiting command issues its T3 session for the timeout plus two minutes, and revokes it when it ends.
threads set changes an existing thread's settings without sending a message. threads send takes the same flags and applies them before the message's turn starts:
t3code threads set --thread <thread-id> --thinking-effort xhigh --speed fast
t3code threads set --thread <thread-id> --model gpt-6-astra --mode plan
t3code threads set --thread <thread-id> --option contextWindow=1m --dry-run
printf '%s' "Continue with the migration." \
| t3code threads send --thread <thread-id> --stdin --model claude-opus-5-5 --thinking-effort max--thinking-effort and --speed set whichever option the model uses for them:
- Codex:
reasoningEffort, andserviceTier, where fast ispriority. - Claude:
effortandfastMode. Only Opus models have fast mode. - Grok:
reasoningEffort. OpenCode:variant.
--option id=value sets any other model option, such as Claude's contextWindow. The CLI checks every value against T3's model catalog, which t3code models list prints. When the model changes, settings the new model supports carry over and the rest are dropped. A T3 server without the catalog gets every effort alias, unchecked.
--permission and --mode build|plan change the thread's permission and plan mode. A permission change restarts a live provider session, so the CLI refuses it while a turn runs. send with new settings also waits for an idle thread, because a message sent mid-turn can join the running turn and keep its old settings. T3 keeps a started conversation on its provider, so --provider only switches between instances of the same driver that share resume state; hand the work over to a new thread to use another provider. Every turn the CLI sends carries the thread's model selection, because that is how T3 applies a change to a live session.
t3code threads interrupt --thread <thread-id>
t3code threads approve --thread <thread-id> --scope session --wait
t3code threads decline --thread <thread-id> --cancel
t3code threads answer --thread <thread-id> --answer "Keep a Changelog" --wait
t3code threads answer --thread <thread-id> --answer 1=main --answer 2=lint
t3code threads answer --thread <thread-id> --dismissinspect and wait list the approvals and questions a thread waits for, with their request ids. With one pending request the commands pick it; with several, pass --request <request-id>.
interruptstops the running turn. It refuses an idle thread, because interrupting Claude stops its whole session.approveaccepts once by default.--scope sessionkeeps the approval for the rest of the session.--scope alwaysworks only when the request offers it; Claude treats it as a denial otherwise.declinedenies the request and lets the agent continue. With--cancel, Codex also stops the turn.answermatches each answer to the question's options by label or value, and otherwise sends it as free text when the question allows that. Prefix answers with the question number when a request asks several. Codex can ask questions that outlive their turn: answering one starts a new turn, which--waitfollows, and--dismisscloses it without an answer.
Each command waits until T3 shows the provider's response. With --wait, it then waits for the turn to finish or stop again, like send --wait. If that wait times out, the error carries responded: true: the response already went through, so do not send it again.
Manage settlement explicitly without starting a new turn:
t3code threads settle --thread <thread-id>
t3code threads unsettle --thread <thread-id>settle refuses a thread with a running/starting session or a pending approval or user-input request. unsettle marks the thread manually active but does not send a message or start its provider session. Both commands require the server to advertise the threadSettlement capability and wait for the requested lifecycle state to appear in T3's projection before succeeding.
t3code config show
t3code config set projectPolicy existing
t3code config set workspaceMode folder
t3code config set openMode browser
t3code config set threadEnvMode local
t3code config set provider codex
t3code config set model gpt-5.6-sol
t3code config set speedMode fast
t3code config set thinkingEffort xhigh| Setting | Values | Default |
|---|---|---|
projectPolicy |
create, existing |
create |
workspaceMode |
repo, folder |
repo |
openMode |
auto, desktop, browser, none |
auto |
threadEnvMode |
t3, local, worktree |
t3 |
runtimeMode |
approval-required, auto-accept-edits, full-access |
full-access |
interactionMode |
default, plan |
default |
provider |
Configured T3 provider instance id | T3 project selection |
model |
Provider model slug | T3 project selection |
speedMode |
standard, fast |
T3 project selection |
thinkingEffort |
Model-supported effort value | T3 project selection |
projectPolicy: "existing" makes a missing project a hard error. workspaceMode: "folder" uses the exact current folder instead of walking up to the Git root. threadEnvMode: "t3" follows T3's project → t3.json → global local/worktree preference. Explicit CLI config values remain overrides.
T3 0.0.28 and later expose an atomic thread bootstrap contract for new worktrees. --checkout worktree uses it to create the thread, prepare the worktree from the current branch, run the matching setup script, and start the prompt. T3 only runs that bootstrap for WebSocket RPC clients: its HTTP dispatch route ignores it and rejects the turn because the thread does not exist yet. The CLI therefore sends this one command over T3's /ws endpoint, authenticated with a short-lived WebSocket ticket. Like T3's own UI, it asks for a temporary t3code/<hex> branch, which T3 renames once the thread has a title. Worktree creation honors the current installation's explicit newWorktreesStartFromOrigin value; when that value is absent, it uses the installed version's default (false on 0.0.28, true on 0.0.29 and later). A repository without a current branch returns WORKTREE_REQUIRES_BRANCH instead of silently falling back to the current checkout.
t3code --json doctor
t3code config path|show|set
t3code projects list
t3code projects resolve --cwd .
t3code projects ensure --cwd . --project-policy create
t3code threads list --status active --cwd .
t3code threads inspect --thread <thread-id>
t3code threads read --thread <thread-id> --detail answers --turns 3
t3code threads send --thread <thread-id> --stdin --wait
t3code threads wait --thread <thread-id>
t3code threads set --thread <thread-id> --thinking-effort high --speed fast
t3code threads interrupt --thread <thread-id>
t3code threads approve|decline --thread <thread-id>
t3code threads answer --thread <thread-id> --answer <answer>
t3code models list
t3code threads settle --thread <thread-id>
t3code threads unsettle --thread <thread-id>
t3code threads create --stdin
t3code handover --stdin
t3code request get api/orchestration/shell
Every command supports human-readable output. --json produces { "ok": true, "data": ... } on success and a stable error envelope on failure. When a failure wraps an upstream CLI error, such as T3's reason for rejecting a worktree bootstrap, error.cause carries that error's code, message, and details.
Thread targeting uses exit code 3 for a missing target, 4 for a lifecycle/confirmation refusal, 5 when dispatch returned but turn acceptance could not be verified, and 6 when a wait timed out.
The leading slash of a request get path is optional. Git Bash rewrites arguments that start with a slash into Windows paths (/api/... becomes C:/Program Files/Git/api/...), so write api/... there or set MSYS_NO_PATHCONV=1.
Authenticated API requests use Node's native HTTP/HTTPS transport to avoid the bundled Undici parser crash on backpressured responses. Each request owns its connection and closes it after completion or failure. The 30-second deadline covers the response body too; truncated bodies return T3_REQUEST_FAILED. Redirects are reported as T3_API_ERROR rather than followed, and the client does not request compressed responses. Point --origin at the T3 server itself.
Responses are still buffered in memory, so available memory limits the largest response. Large JSON output can be piped to a file; the CLI lets output finish before exiting.
The package ships two skills for coding agents in skills/:
use-t3code-clicovers setup, handovers, and the full command set.t3threadpoints an agent at an existing thread:$t3thread <thread-id> <what to do>. The agent inspects the thread and reads only as much as the instruction needs. It can brief you on the thread, answer questions about it, continue or review its work, or message it and wait for the reply. When you ask, it also changes the thread's model, effort, or mode, stops a running turn, and answers the thread's approvals and questions.
Copy or link a skill folder into your agent's skills directory, such as ~/.claude/skills/ for Claude Code or ~/.agents/skills/ for Codex. A global npm install keeps them in $(npm root -g)/@bvdm/t3code-cli/skills.
This CLI was initially developed for the Delano viewer. Delano's Send to T3 Code button lets someone hand browser context directly to a new thread in the T3 Code chat application.
flowchart LR
Button[Send to T3 Code] --> Endpoint[Local handover endpoint]
Endpoint --> CLI[t3code handover --stdin]
CLI --> Thread[New T3 Code thread]
The CLI is the product; a front end is not required. See the optional integration README for why Delano needed this handover button and how its browser-to-server-to-CLI flow works. That folder also contains a copyable React split button and Node bridge as one example of integrating t3code into another application.
Current stable T3 Code registers t3code:// but only uses a second launch to reveal its window. The CLI therefore creates the exact thread first and reports opened.exactThread: false when it can only reveal today's desktop app. If a T3 build registers the proposed t3://thread/<threadId> protocol, openMode: "auto" uses it and reports exactThread: true. openMode: "browser" opens the exact local web route immediately.
The CLI uses T3's own auth session issue control plane to mint an administrative bearer token, keeps it only in memory, and revokes it in a finally block. A worktree handover also exchanges that token for a short-lived WebSocket ticket. Tokens and tickets are never included in JSON output or logs.
Pull requests and pushes run pnpm check through GitHub Actions on the minimum supported Node 22 and Node 24 versions.
Publishing uses npm trusted publishing from publish.yml. Configure the package's Trusted Publisher once in the npm package settings:
| Field | Value |
|---|---|
| Provider | GitHub Actions |
| Organization or user | MajesteitBart |
| Repository | t3code-cli |
| Workflow filename | publish.yml |
| Environment | Leave empty |
The workflow uses short-lived OIDC credentials, so it does not need an NPM_TOKEN repository secret. It also publishes npm provenance automatically.
To release a new version:
npm version patch
git push --follow-tagsThen publish a GitHub Release for the new v<package-version> tag. The workflow verifies that the tag matches package.json, installs from the frozen lockfile, runs the complete prepublishOnly check, and publishes the public scoped package to npm.
