| name | t3ex-config-help |
| description | Configure T3 Code itself — add project scripts (the buttons in the scripts menu), bind keyboard shortcuts, and adjust app settings. Use when the user wants a new T3 Code action, script, shortcut, keybinding, project icon, or a change to how T3 Code behaves. |
| disable-model-invocation | true |
| argument-hint | <what to configure> |
Configure T3 Code
T3 Code reads configuration from three places. Two are hand-editable; one is
owned by the app.
| What | Where | Edit by hand? |
|---|
| Project scripts, project icon | t3.json at the repo root | Yes — commit it |
| Keyboard shortcuts | ~/.t3/userdata/keybindings.json | Yes |
| App settings, providers, models | ~/.t3/userdata/settings.json | No — use the UI |
A dev-mode server (vp run dev) uses ~/.t3/dev/ instead of ~/.t3/userdata/.
The published docs say keybindings live at ~/.t3/keybindings.json. That is
wrong — the code joins the filename to the state directory, so the real path
is ~/.t3/userdata/keybindings.json. Editing the documented path does nothing.
After any edit, check your work:
python3 validate.py
python3 validate.py path/to/t3.json
This matters more than usual here: T3 Code silently ignores invalid rules and
invalid files. A typo means your shortcut quietly never fires, with only a
warning in the server log.
Project scripts — the "actions" in the scripts menu
Scripts are the buttons in T3 Code's scripts menu. They run in a T3 Code
terminal at the project root, and they are checked into the repo so the whole
team gets them.
{
"$schema": "https://t3.codes/schema/t3.json",
"iconPath": "assets/logo.svg",
"scripts": [
{
"name": "Dev server",
"command": "pnpm dev",
"icon": "play",
"previewUrl": "http://localhost:3000",
"autoOpenPreview": true
},
{
"name": "Setup Worktree",
"command": "pnpm install && ln -sf $T3CODE_PROJECT_ROOT/.env .env",
"icon": "configure",
"runOnWorktreeCreate": true
}
]
}
| Field | Required | Notes |
|---|
name | yes | Shown in the menu |
command | yes | Shell command, run at the project root |
icon | no | One of play, test, lint, configure, build, debug. Defaults to play |
runOnWorktreeCreate | no | Runs automatically after a worktree is created |
previewUrl | no | Opens in the preview panel. Desktop app only |
autoOpenPreview | no | Opens the preview the moment the script starts. Needs previewUrl |
Limits: 50 scripts, iconPath at most 512 characters.
Two environment variables
Scripts get these injected:
T3CODE_PROJECT_ROOT — the project's main directory, always set
T3CODE_WORKTREE_PATH — the worktree path, only when running in a worktree
This distinction is the point. A script's working directory is the worktree
when one exists, so T3CODE_PROJECT_ROOT is how you reach back to the main
checkout — which is how you symlink a gitignored .env into a fresh worktree.
Gotchas that cost real time
- Only the first
runOnWorktreeCreate script runs. Marking a second one has
no effect and no warning. Combine them into one setup script with &&.
previewUrl is desktop-only. In a browser tab it is ignored and you open
the URL yourself.
autoOpenPreview without previewUrl does nothing.
- Scripts are shared with everyone who opens the repo. Don't put machine-specific
paths or secrets in a
command.
Keyboard shortcuts
~/.t3/userdata/keybindings.json is a JSON array. Your file replaces the
defaults wholesale, so copy the defaults first if you only want to change one
thing.
[
{ "key": "mod+j", "command": "terminal.toggle" },
{ "key": "mod+shift+d", "command": "terminal.splitVertical", "when": "terminalFocus" },
{ "key": "mod+1", "command": "script.browser.run" }
]
key and command are required; when is optional. Maximum 256 rules.
Modifiers: mod (⌘ on macOS, Ctrl elsewhere), cmd/meta, ctrl/control,
shift, alt/option.
Commands:
sidebar.toggle rightPanel.toggle commandPalette.toggle
terminal.toggle terminal.split terminal.splitVertical
terminal.new terminal.close diff.toggle
preview.toggle preview.refresh preview.focusUrl
preview.zoomIn preview.zoomOut preview.resetZoom
chat.new chat.newLocal editor.openFavorite
modelPicker.toggle modelPicker.jump.1-9
thread.previous thread.next thread.jump.1-9
script.{id}.run
when contexts: terminalFocus, terminalOpen, previewFocus,
previewOpen, modelPickerOpen. Operators: !, &&, ||, parentheses.
Unknown context names evaluate to false, so a typo disables the binding
rather than erroring.
Precedence: rules are evaluated in array order, and for a given key press the
last matching rule wins — across all commands, not just within one. If a
shortcut isn't working, look for a later rule that shadows it.
Binding a key to your own script
script.{id}.run needs the script's id, which is not in t3.json — T3 Code
assigns it when the script is imported, as a lowercase slug of the name
("Browser" → browser). Read the real ids from the state database:
sqlite3 ~/.t3/userdata/state.sqlite \
"SELECT title, scripts_json FROM projection_projects WHERE scripts_json != '[]';"
The id must match ^[a-z0-9][a-z0-9-]*$ and be at most 24 characters. validate.py
cross-checks the ids you reference against the ones that actually exist.
App settings
~/.t3/userdata/settings.json holds app behavior and provider configuration:
enableAssistantStreaming, enableProviderUpdateChecks
automaticGitFetchInterval, newWorktreesStartFromOrigin
defaultThreadEnvMode — local or worktree, for new threads
addProjectBaseDirectory — where the "add project" picker starts
textGenerationModelSelection — the model used for commit messages and titles
providers / providerInstances — per-driver config for codex, claudeAgent,
cursor, grok, opencode, including binary paths and custom models
observability
Change these in the UI, not the file. The server owns this file and rewrites
it; a hand edit made while T3 Code is running will be overwritten without
warning. Read it to answer questions, and to confirm a settings change landed.
Note this file configures which providers T3 Code can drive. It does not
configure the agents themselves — Codex still reads ~/.codex/config.toml, and
MCP servers are configured per-provider, not in T3 Code.
Answering "why isn't my config working?"
Work down this list:
- Run
validate.py — it catches most of it.
- Check the path.
~/.t3/keybindings.json (documented) is not the real one.
- For a shortcut, look for a later rule with the same key — last match wins.
- For a
when clause, confirm every context name is in the list above.
- For a script, restart or reopen the project so
t3.json is re-read.
- Check the server log for parse warnings:
~/.t3/userdata/logs/server.log.