| name | t3ex-fork-thread |
| description | Continue an existing T3 Code thread in the current session, optionally cutting off everything from a given message onward. Use when the user supplies a T3 Code thread id and wants to resume, fork, retry, or hand that conversation to a different model. |
| disable-model-invocation | true |
| argument-hint | <thread-id> [HH:MM] [through] [instruction…] |
Fork a T3 Code Thread
Load a past T3 Code conversation into this session so you can carry on with it —
usually under a different model than the one that ran it.
T3 Code has no CLI or API for posting into an existing thread. Forking therefore
means reading the old thread and continuing the work here, in a new one. The
original is never modified; the extractor opens the database read-only.
Invocation
/t3ex-fork-thread <thread-id> [time] [through] [instruction…]
The user copies the thread id from the T3 Code sidebar. A unique prefix is
enough — ce419dc7 works as well as the full UUID.
Run the extractor
extract.py sits next to this file. Run it from this skill's directory:
python3 extract.py <thread-id> [--at HH:MM[:SS]] [--through] [--turn N]
Python 3.10+, no dependencies.
| Flag | Effect |
|---|
--at HH:MM | Local wall-clock time of a user message, exactly as the sidebar shows it |
--through | Keep the message at --at instead of dropping it |
--turn N | Keep the first N turns, ignoring --at |
--list-turns | Print the numbered turn list and exit |
--output T.N | Print one withheld command output, e.g. --output 1.3 |
--db PATH | Point at a specific state.sqlite |
--cwd PATH | Directory to compare against the thread's repo (default: current) |
--max-diff-lines N | Cap per-file diff lines (default 40) |
Where the cut falls
A bare time drops that message and everything after it. The user is pointing
at where the thread went wrong, so the message they name is excluded.
/t3ex-fork-thread ce419dc7 07:58 → extract.py ce419dc7 --at 07:58
If they say through, keep, or including, the named message is kept
and only what follows is dropped:
/t3ex-fork-thread ce419dc7 through 07:58 → extract.py ce419dc7 --at 07:58 --through
With no time at all, the whole thread loads.
Times are local, matching the sidebar. The script converts to UTC itself — never
hand it a UTC time.
When a minute is ambiguous
Two user messages can share a minute. The script then exits 2 and lists the
candidates rather than guessing:
error: 2 user messages share the minute 07:58:
turn 2 07:58:45 add a command in root of this repo to run the new pr…
turn 3 07:58:54 or npm run dev
Re-run with seconds (--at HH:MM:SS) or with --turn N.
Show the user those candidates and ask which one they meant. Do not pick for
them.
What to do once it is loaded
Thread id alone — read the transcript, then give a short orientation: what
was done, what state the repo is in, what was left open. Stop there and wait.
The user is checking that you picked up the thread correctly before you touch
anything.
Thread id plus an instruction — load the context, then act on the
instruction in the same turn.
Before you edit anything
The header prints the thread's repo and your current directory. If they differ
it says so loudly:
repo /Users/madda/dev/active/Maestro3
cwd /Users/madda/dev/active/t3code
*** WORKING DIRECTORY DIFFERS FROM THE THREAD'S REPO ***
Reading a thread from another repo is fine. Editing is not — stop and
confirm with the user first. Applying a Maestro3 change while sitting in t3code
is the failure this guards against.
What is included
Full conversation text, plus a one-line trace per tool call — the command that
ran, its exit status, and any file diffs. Captured stdout is withheld and its
size reported, because it dominates the data: a thread with 1.1 MB of raw
activity extracts to about 39 KB.
Pull a specific withheld output only when it actually matters:
python3 extract.py <thread-id> --output 1.3
Escape hatch — the schema
For anything the script does not cover, query the database directly. It lives at
~/.t3/userdata/state.sqlite (~/.t3/dev/userdata/state.sqlite for a dev
home). Open it read-only.
| Table | Columns that matter |
|---|
projection_threads | thread_id, title, project_id, model_selection_json, fork_source_thread_id, deleted_at |
projection_projects | project_id, title, workspace_root |
projection_turns | turn_id, pending_message_id, assistant_message_id, requested_at, checkpoint_ref |
projection_thread_messages | message_id, turn_id, role, text, created_at |
projection_thread_activities | turn_id, kind, summary, payload_json, created_at |
Four traps worth knowing before you write your own query:
- User messages have a NULL
turn_id. They attach only through
projection_turns.pending_message_id. Grouping on turn_id silently loses
every user prompt.
sequence is NULL on every activity row. Order by created_at only.
- One assistant reply is often several rows. Streamed chunks share a
turn_id; join them in timestamp order.
assistant_message_id can dangle. An interrupted turn points at a message
row that does not exist.
Timestamps are ISO-8601 UTC. payload_json shapes vary by
json_extract(payload_json, '$.itemType'): command_execution, file_change,
dynamic_tool_call, mcp_tool_call, web_search, image_view.
Treat every projection table as read-only. The event log
(orchestration_events) is the source of truth and writing to projections
corrupts state.
Related
madda-search-conversation-history covers the file-based side — Codex and
Claude .jsonl transcripts. Use that when there is no T3 Code thread id.