You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Feature]: Open a thread from the provider's own session id, for tools that learn about sessions from Claude Code or Codex
#18614
Thank you for #18454. t3code://threads/<environmentId>/<threadId> works here: with the app running, each link opened its thread within 2.5 s.
A tool outside T3 Code can rarely build that link, though. It knows a session by the provider's own id, never by T3's thread id:
Claude Code sends session_id to every hook and writes it to ~/.claude/sessions/<pid>.json.
Codex sends its thread id to its hooks.
T3 Code itself starts claude --session-id=<id> for a new thread and claude --resume=<id> for a resumed one.
I looked for T3's thread id everywhere such a tool can see. I checked 0.0.46-nightly.20261012.3010 on macOS, with two Claude threads running at once and the app launched with a clean environment:
Thread "Selam"
Thread "Hop"
T3 thread id
46aeaeea-…
3ca7f6c7-…
Claude session_id (hooks, session file)
027879fb-…
315c3ba9-…
claude argv
--session-id=027879fb-…
--resume=315c3ba9-…
claude environment
24 variables
the same 24, with the same values
Environment. The two claude processes have identical environments. The Agent SDK adds three variables to the server's environment: CLAUDE_AGENT_SDK_VERSION, CLAUDE_CODE_ENTRYPOINT=sdk-ts and NoDefaultCurrentDirectoryInExePath. Nothing names the thread, the environment or the project.
The database. Only statev2.sqlite maps one id to the other, through orchestration_v2_projection_provider_threads.payload_json → nativeThreadRef.nativeId. In 0.0.45 the file was state.sqlite, with the cursor in another table.
The active provider thread id. It is not derived from the native id: it reads …native-thread:pending:run:thread:<threadId>:ordinal:1.
The id the tool has. Put into the link in place of the thread id, Claude's session_id opens an empty "New thread".
So a tool can see "Claude session 027879fb-… is waiting for permission" and bring T3 Code forward, but it cannot show that thread.
Who needs it
Mine: Evlat. A macOS status strip that shows each Claude Code and Codex session at the edge of the screen. It learns about sessions from the agents' hooks. Its "Go to session" already brings T3 Code forward by walking the agent's process tree up to the app. It cannot select the thread.
The other uses in [Feature]: Open a specific desktop thread via t3code:// #6834. A Stream Deck plugin and an agent skill get their thread ids by reading T3's own state; the skill searches the projection tables. That works for a tool that reads T3. A tool that only sees the agent has nothing to read.
driver is T3's own name for the provider, as in nativeThreadRef.driver (claudeAgent, codex). The pair is exactly what nativeThreadRef stores, so the lookup is exact.
Resolution. T3 opens the app thread that owns the provider thread with that nativeThreadRef. This includes a provider thread that is no longer the active one after a fork, a reset or a provider switch.
Environment. An optional ?environment=<environmentId> pins the machine. Without it, the local environment is asked first. A tool on this Mac can also read the id from GET /.well-known/t3/environment.
An unknown session reveals the window and changes nothing. No "New thread" draft opens. A claude started by hand in T3's integrated terminal has no thread, and a status app will send its id anyway.
Like thread links, it never sends anything. It needs no token, because the lookup happens inside T3 Code.
The exact shape is yours. A form under threads/ works just as well. What matters to a tool is the input: {driver, nativeId}.
A smaller step that only helps Claude. T3 could export the thread's link into the provider process: T3CODE_THREAD_URL=t3code://threads/<environmentId>/<threadId>. Every Claude thread has its own process, so the value is exact, and hooks inherit it. Terminals already do this; see below. It cannot work for Codex, because one codex app-server serves every thread of an instance. That is why I lead with the link.
Smallest useful scope
Parse t3code://sessions/<driver>/<nativeId> next to new and threads in packages/shared/src/promptLinks.ts. The desktop already queues every link that parser accepts (DesktopClerk.openAppLink).
Resolve {driver, nativeId} to a thread on the server, from the provider threads' nativeThreadRef.
Navigate to /$environmentId/$threadId when found. Otherwise only reveal the window.
Document it beside thread links in docs/user/prompt-links.md.
GET /api/orchestration/shell with a bearer token from t3 auth session issue. A status app would have to keep a token. Also, in the stored projection the active provider thread id is not the native id; I did not check the API response itself.
GUI scripting. It needs the Accessibility permission, depends on the locale, and breaks when two threads share a title.
Risks or tradeoffs
Uniqueness. A fork or a rollback reset creates a new Claude session id. As far as I can see, each native id belongs to one provider thread, and so to one app thread.
Exposure. Session ids are UUIDs, and the link only focuses a thread the user already has. It is the same trust as thread links.
Contract. The input becomes a public contract, so it should stay {driver, nativeId} even if storage changes again.
Examples or references
Apps that open a session by the agent's own id, which is what this proposal asks for:
Claude Code's VS Code extension.vscode://anthropic.claude-code/open?session=<session_id> takes Claude Code's own session id. Its docs say: "If the session is already open in a tab, that tab is focused." (docs)
The Codex app.codex://threads/<id> opens a local Codex conversation by Codex's own id. It appears to do nothing when the lookup fails. (openai/codex#21779)
Hosts that hand the agent their own address, which is the smaller step above:
Warp. It exports WARP_FOCUS_URL=warp://session/<uuid> into every session. Its PR describes the use case: "External notifier scripts can use WARP_FOCUS_URL as an opaque jump-back target." (warpdotdev/warp#11130)
Claude's desktop app. It gives each Claude Code session it starts CLAUDE_CODE_HOST_SESSION_ID=local_<uuid>, and claude://code/continue?session=<that id> opens the session. This is what its own "Continue" uses. It is undocumented; I read it from the app bundle, and Evlat uses it today.
Terminals and multiplexers.
iTerm2: ITERM_SESSION_ID, focused with iterm2:reveal?sessionid=.
cmux: CMUX_WORKSPACE_ID and CMUX_SURFACE_ID, focused with cmux://workspace/<id>/surface/<id>.
WezTerm: WEZTERM_PANE, focused with wezterm cli activate-pane.
kitty: KITTY_WINDOW_ID, focused with kitten @ focus-window.
tmux: TMUX_PANE.
The same need elsewhere.claude-status-bar, a menu bar app, "can detect sessions via hooks (session_id is exposed) but cannot take the user to the conversation" in Claude's desktop app (anthropics/claude-code#85042).
Contribution
I would be open to helping implement this.
I can make the change in the shape you pick. I would attach a recording from a packaged build of a session link, both into a running app and as the event that launches it. Cold launch currently drops every link; I reported that separately in #18613.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Before submitting
Area
apps/desktop, apps/web, apps/server, packages/shared
Problem or use case
Thank you for #18454.
t3code://threads/<environmentId>/<threadId>works here: with the app running, each link opened its thread within 2.5 s.A tool outside T3 Code can rarely build that link, though. It knows a session by the provider's own id, never by T3's thread id:
session_idto every hook and writes it to~/.claude/sessions/<pid>.json.claude --session-id=<id>for a new thread andclaude --resume=<id>for a resumed one.I looked for T3's thread id everywhere such a tool can see. I checked 0.0.46-nightly.20261012.3010 on macOS, with two Claude threads running at once and the app launched with a clean environment:
46aeaeea-…3ca7f6c7-…session_id(hooks, session file)027879fb-…315c3ba9-…claudeargv--session-id=027879fb-…--resume=315c3ba9-…claudeenvironmentclaudeprocesses have identical environments. The Agent SDK adds three variables to the server's environment:CLAUDE_AGENT_SDK_VERSION,CLAUDE_CODE_ENTRYPOINT=sdk-tsandNoDefaultCurrentDirectoryInExePath. Nothing names the thread, the environment or the project.statev2.sqlitemaps one id to the other, throughorchestration_v2_projection_provider_threads.payload_json → nativeThreadRef.nativeId. In 0.0.45 the file wasstate.sqlite, with the cursor in another table.…native-thread:pending:run:thread:<threadId>:ordinal:1.session_idopens an empty "New thread".So a tool can see "Claude session
027879fb-…is waiting for permission" and bring T3 Code forward, but it cannot show that thread.Who needs it
statev2.sqlitefrom outside costs: pinning migrations and column fingerprints, mirroring the archive and settle filters, and POSIX locks on-shm.Proposed solution
Let a link name the thread by its provider session. T3 Code resolves it, then navigates as a thread link does today. For example:
driveris T3's own name for the provider, as innativeThreadRef.driver(claudeAgent,codex). The pair is exactly whatnativeThreadRefstores, so the lookup is exact.nativeThreadRef. This includes a provider thread that is no longer the active one after a fork, a reset or a provider switch.?environment=<environmentId>pins the machine. Without it, the local environment is asked first. A tool on this Mac can also read the id fromGET /.well-known/t3/environment.claudestarted by hand in T3's integrated terminal has no thread, and a status app will send its id anyway.The exact shape is yours. A form under
threads/works just as well. What matters to a tool is the input:{driver, nativeId}.A smaller step that only helps Claude. T3 could export the thread's link into the provider process:
T3CODE_THREAD_URL=t3code://threads/<environmentId>/<threadId>. Every Claude thread has its own process, so the value is exact, and hooks inherit it. Terminals already do this; see below. It cannot work for Codex, because onecodex app-serverserves every thread of an instance. That is why I lead with the link.Smallest useful scope
t3code://sessions/<driver>/<nativeId>next tonewandthreadsinpackages/shared/src/promptLinks.ts. The desktop already queues every link that parser accepts (DesktopClerk.openAppLink).{driver, nativeId}to a thread on the server, from the provider threads'nativeThreadRef./$environmentId/$threadIdwhen found. Otherwise only reveal the window.docs/user/prompt-links.md.No change to auth, providers or stored data.
Alternatives considered
statev2.sqlite. It works today. It is internal, it was renamed within one version, it can be many GB, and Read-only thread metadata listing for local tools (thread IDs, titles and provider session IDs) #17593 lists the traps.GET /api/orchestration/shellwith a bearer token fromt3 auth session issue. A status app would have to keep a token. Also, in the stored projection the active provider thread id is not the native id; I did not check the API response itself.Risks or tradeoffs
{driver, nativeId}even if storage changes again.Examples or references
Apps that open a session by the agent's own id, which is what this proposal asks for:
vscode://anthropic.claude-code/open?session=<session_id>takes Claude Code's own session id. Its docs say: "If the session is already open in a tab, that tab is focused." (docs)codex://threads/<id>opens a local Codex conversation by Codex's own id. It appears to do nothing when the lookup fails. (openai/codex#21779)Hosts that hand the agent their own address, which is the smaller step above:
WARP_FOCUS_URL=warp://session/<uuid>into every session. Its PR describes the use case: "External notifier scripts can useWARP_FOCUS_URLas an opaque jump-back target." (warpdotdev/warp#11130)CLAUDE_CODE_HOST_SESSION_ID=local_<uuid>, andclaude://code/continue?session=<that id>opens the session. This is what its own "Continue" uses. It is undocumented; I read it from the app bundle, and Evlat uses it today.ITERM_SESSION_ID, focused withiterm2:reveal?sessionid=.CMUX_WORKSPACE_IDandCMUX_SURFACE_ID, focused withcmux://workspace/<id>/surface/<id>.WEZTERM_PANE, focused withwezterm cli activate-pane.KITTY_WINDOW_ID, focused withkitten @ focus-window.TMUX_PANE.The same need elsewhere. claude-status-bar, a menu bar app, "can detect sessions via hooks (
session_idis exposed) but cannot take the user to the conversation" in Claude's desktop app (anthropics/claude-code#85042).Contribution
I can make the change in the shape you pick. I would attach a recording from a packaged build of a session link, both into a running app and as the event that launches it. Cold launch currently drops every link; I reported that separately in #18613.
All reactions