| written_by | ai |
| name | thread-necromancy |
| description | Use only when Josh explicitly invokes $thread-necromancy or explicitly asks for thread necromancy. Recreate, rehydrate, promote or repair Codex tasks through the local app-server. Do not use merely because Josh provides a task ID or asks to read, inspect or continue a task; use ordinary task tools instead. |
Thread Necromancy
This skill is an evidence workflow. Do not treat titles, search results, or old helper output as proof.
Hard Rules
- Only use this skill after the user explicitly asks for thread necromancy. Do not proactively inspect, repair, rehydrate, fork, or promote threads with this skill.
- Use supported app-server APIs for thread creation and ordinary state changes. Do not hand-edit
logs_*.sqlite or .codex-global-state.json. The only allowed direct rollout edit is the helper's backed-up synthetic event_msg insertion immediately after session_meta after thread/resume(history) side recovery, because the desktop list path only indexes rollout files that have visible user events near the file head. The only allowed direct state DB edit is the helper's backed-up app-index repair for promoted side threads, because current app-server has no preview/first-user-message metadata API and a JSONL-only thread is not a user-facing promotion.
- Promoting or rehydrating must not start a model turn. If a verification turn is needed, create a separate test thread and archive it.
- Do not call a
/side promotion successful unless full history is preserved. Prompt-history snippets, logs, or a handover prompt are not full history.
- Do not call a promotion successful merely because a target rollout exists. The target must pass
thread/read plus app-server thread/list/search proof. If a target is created but that proof fails, archive the target and report PROMOTION_FAILED_APP_VISIBILITY.
- Do not call a rehydrate successful unless old model-visible instruction items were filtered and a fresh-turn smoke has proved current AGENTS can be read.
- Clean up failed and verification threads with
thread/archive; report exact ids and archive state.
Helper
Use the bundled helper:
node ~/.codex/skills/thread-necromancy/scripts/thread-necromancy.mjs list --query gemini --limit 20
node ~/.codex/skills/thread-necromancy/scripts/thread-necromancy.mjs read --thread-id <id> --include-turns
node ~/.codex/skills/thread-necromancy/scripts/thread-necromancy.mjs rehydrate --thread-id <id> --title "Rehydrated: ..."
node ~/.codex/skills/thread-necromancy/scripts/thread-necromancy.mjs side-diagnose --thread-id <id>
node ~/.codex/skills/thread-necromancy/scripts/thread-necromancy.mjs raw-log-diagnose --thread-id <id>
node ~/.codex/skills/thread-necromancy/scripts/thread-necromancy.mjs promote --thread-id <id> --title "Promoted: ..."
node ~/.codex/skills/thread-necromancy/scripts/thread-necromancy.mjs repair-app-index --thread-id <id> --title "Promoted: ..."
node ~/.codex/skills/thread-necromancy/scripts/thread-necromancy.mjs archive --thread-id <id>
node ~/.codex/skills/thread-necromancy/scripts/thread-necromancy.mjs self-test
The helper initializes app-server with capabilities.experimentalApi: true.
Run self-test after editing the helper; it exercises instruction filtering,
synthetic event head insertion, tail-event repair, and app-list visibility
gating without touching live Codex state.
promote now runs the same evidence gate as side-diagnose first. If the side
thread has no rollout but logs contain, or SQLite overflow pages can recover, a
full outbound response.create.input array with the side boundary and
post-boundary history, the helper uses that raw Responses history as the source.
It then inserts backed-up synthetic event_msg rows immediately after the new
rollout's session_meta so thread/read shows visible turns and thread/list
indexes the thread, while the raw response_item history preserves tool calls
and model continuity. Appending these rows at the tail is not enough for current
desktop builds; the list/index scan may ignore the rollout and leave Josh with a
JSONL artifact that is not visible as a chat. The helper also repairs the app
index metadata (first_user_message, preview, has_user_event,
thread_source, and updated_at) with a backed-up SQLite update as a cache
nudge, then proves the promoted thread is discoverable through thread/list
unfiltered and by search term. Later app-server list scans may rebuild the
SQLite row from the rollout and put the first prompt back into the internal
title column; do not use that internal row alone as success or failure. Binding
proof is the user-facing list/search result plus thread/read visible turns.
If it cannot find a full-history source, it returns
PROMOTION_BLOCKED_FULL_HISTORY_UNAVAILABLE and creates no thread. If it
creates a target but cannot prove app visibility, it archives that target and
returns PROMOTION_FAILED_APP_VISIBILITY instead of reporting success.
Open-source Codex does not have a Pi-style single session.json for these
threads. The cloneable substrate is raw ResponseItem history in the local
rollout/thread store, a full outbound model request snapshot, a replayable
rollout-trace bundle, or a live in-memory export from the owning app-server.
For /side, upstream deliberately sets ephemeral = true; ephemeral sessions
skip thread persistence, and the TUI installs an empty visible side snapshot
even though core keeps the forked history model-visible.
Proven Rehydrate Flow
Use thread/resume(history) rather than thread/fork when the goal is fresh AGENTS/system context.
What the helper does:
- Reads the source thread through app-server.
- Extracts raw
response_item.payload entries from the source JSONL.
- Drops old instruction items:
message items with role: "developer"
- user messages containing
# AGENTS.md instructions
- user messages containing
<environment_context>
- Calls
thread/resume with the filtered raw Responses history and the source cwd.
- Names the new thread if requested.
- Verifies the new JSONL has no old developer/AGENTS/env items and no
turn_context.
Expected result:
- The new thread has a normal state row and a JSONL containing model-visible raw history.
has_user_event=0, turn_context=0, and thread/read includeTurns:true returns zero prior UI turns.
- This is still useful: a later model turn receives the copied raw history plus freshly discovered current AGENTS from the target cwd.
- It is not a user-facing turn-history clone; the app-server currently does not reconstruct prior UI turns from raw history.
Verification smoke already proven locally:
- A synthetic
thread/resume(history) thread with zero UI turns was followed by a separate verification turn.
- The model saw both prior injected history tokens and a fresh AGENTS token.
- A second real-cwd smoke against
/Users/josh/code/nix/AGENTS.md returned the historical name lineage from the current AGENTS file, then the verification thread was archived.
Plain Fork
Use thread/fork only when preserving a normal stored thread as-is is acceptable.
Plain fork is not a sanitized rehydrate:
- It copies old model-visible
# AGENTS.md instructions.
- It may create a fresh wrapper, but old instruction history remains in context.
- Do not use it to claim fresh AGENTS unless you have separately proved the old block is absent or harmless for the goal.
/side Promotion
The user-facing requirement for /side promotion is full continuity, not a
handover. A valid promoted thread must have the complete source history:
all user messages, assistant messages, tool calls, tool outputs, and relevant
metadata. Starting a model turn to "summarize" or "continue" is not migration.
First classify the source:
- Normal stored thread: has a
threads row and app-server thread/read works. Use promote/fork only if preserving that stored history is the goal.
- Subagent worker: has
thread_source: subagent or source.subagent.thread_spawn. Label it as subagent promotion, not /side.
- True ephemeral side chat: may have prompt-history and log rows, but no
threads row, no rollout JSONL, and app-server thread/read returns thread not loaded.
Run side-diagnose before any promotion attempt. It is read-only and checks:
- app-server
thread/read and thread/loaded/list
state_*.sqlite threads rows and rollout JSONL audits
.codex-global-state.json and .bak prompt-history shape without dumping contents
logs_*.sqlite exact thread rows, non-exact body hits, response ids, event types, and whether input/message/tool history is present
- full vs continuation-only
response.create requests; previous_response_id is not full history by itself
- local rollout-trace bundle candidates under
CODEX_ROLLOUT_TRACE_ROOT, ~/.codex/rollout-traces, /tmp/codex-rollout-traces, and /private/tmp/codex-rollout-traces
- the owner process recorded in logs, including whether it is alive and whether it has a side rollout open
Use raw-log-diagnose when debugging recovery sources. It reports raw SQLite/WAL
scan results separately from recovered request snapshots; the combined
recoverable result may be true even when raw scan alone is continuation-only.
For true ephemeral side chats:
- Full-history promotion is unsupported through local app-server when no rollout JSONL/state row, live full-history export, or full logged
response.create.input exists.
- Logs are a valid recovery source only when they contain the full outbound model input array, not just deltas, response ids, or prompt-history snippets.
previous_response_id rows can be expanded only by a replayable rollout-trace bundle or a remote Responses input-items API with sufficient read scope. Do not materialize a thread from continuation anchors alone; Codex ResponseItem has no persisted previous-response-anchor variant.
.codex-global-state.json prompt-history may contain user prompts only.
- Do not create a partial normal thread from prompt-history/log fragments when Josh requires full history.
- The missing product/API contract is one of: expose a read/export/promote API for live ephemeral side threads; record durable full snapshots for side threads; enable rollout-trace and provide a trace-to-ResponseItem exporter; or provide a remote input-items reader with auth scope sufficient to reconstruct full history without starting a model turn.
If the side thread is still live and app-server or renderer state exposes full
raw history, a valid implementation may materialize it with thread/resume(history)
or an equivalent app-server promotion primitive, then verify history counts and
that no migration turn was started. If the side thread has already expired and
only prompt-history/log fragments remain, stop at the blocked diagnostic.
If side-diagnose.fullHistory.source == "websocket_request_input", promotion is
allowed. The helper must:
- Extract the latest full
response.create.input array for the source thread,
including through raw SQLite/WAL overflow recovery when ordinary SQL rows only
show continuation requests.
- Verify it contains the side boundary plus post-boundary user and assistant/tool history.
- Call
thread/resume(history) with that raw input array.
- Back up the new rollout and insert synthetic
event_msg rows immediately
after session_meta, derived from the recovered user/assistant response items
so the promoted thread is visible in thread/read and indexable by
thread/list without starting a model turn.
- Back up and repair the app index metadata so the promoted thread has a first
user message, preview, user thread source, user-event marker, and fresh updated
timestamp in the sidebar/search state.
- Name the new thread if requested.
- Read the target thread and audit the target JSONL for copied response items and zero
turn_context rows.
- Verify
thread/list finds the target unfiltered and by title/search term. If
the target is readable by id but absent from thread/list, repair the visible
event rows near the rollout head and rerun the list/search proof before
claiming success.
- If list/search proof still fails after repair, archive the created target and
report a failed promotion rather than leaving a misleading partial thread.
- Treat SQLite
threads metadata repair as best-effort cache repair. If a later
thread/list scan rewrites the internal title/preview from the first prompt
but the target remains visible by title/search and opens with the reconstructed
turns, the promotion is still user-facing successful.
- Call
thread/unsubscribe on the new target immediately.
- Report the source log row, copied item count, target thread id, target rollout path, app-list visibility evidence, and
noModelTurnStarted.
Evidence Checklist
Before claiming success, check:
- Source id, title/preview, cwd, rollout path, archive state,
thread_source, source, and spawn edges.
- Whether app-server can
thread/read the source.
- JSONL line counts and
response_item types.
- For
/side, whether side-diagnose.fullHistory.recoverable is true before mutation.
- For
/side, whether the promoted target is findable through app-server thread/list, not just readable from a JSONL path.
- Whether old
role: developer, # AGENTS.md instructions, or <environment_context> items remain after rehydrate.
- Whether any
turn_context or user event was created during migration.
- For side chats, whether full assistant/tool history exists locally. If not, refuse full promotion.
Invalid Success Claims
These are failures:
- Promoting a subagent worker when the user asked for a
/side chat.
- Creating a new thread with only a handover prompt or prompt-history snippets.
- Saying “fresh AGENTS” after a plain fork that still contains old AGENTS blocks.
- Starting an unsolicited model turn during migration.
- Leaving failed or verification artifacts unarchived.
Reporting Labels
rehydrate: filtered raw history copied through thread/resume(history) with no model turn; current AGENTS applies on the next real turn.
plain fork: stored thread copied as-is; old instruction blocks may remain.
promotion: normal stored source forked to a non-ephemeral user thread with expected stored history.
side-diagnose: read-only full-history recoverability audit; creates no thread and starts no turn.
blocked side promotion: true ephemeral source lacks full persisted history or app-server read support.
verification thread: intentionally started only to prove model-visible behavior; must be archived.