thread-archiver archives Codex threads through the official codex app-server
JSON-RPC API, writes an incremental JSON manifest, and uses Bubble Tea for its
terminal UI.
On an archive run, the command:
- Uses
--dbwhen provided; otherwise scans the selected Codex home for regularstate_*.sqlitefiles. One discovered database is selected automatically; multiple discovered databases are shown in a single-choice picker. - Reads the initial set of active threads from the selected database file.
- Groups those threads by project, where project identity is the normalized
cwdvalue. - Shows an interactive Bubble Tea project picker with no projects selected.
- Keeps only the explicitly selected projects.
- Starts
codex app-server --listen stdio://. - Performs the required
initializeandinitializedhandshake. - Archives threads one at a time with
thread/archive. - Verifies that each archive actually took effect.
- Rewrites a JSON manifest after every processed thread.
With --dry-run, the command still reads threads and rewrites the manifest, but
it does not start codex app-server, does not perform the handshake, and does
not send thread/archive.
The command does not move rollout files directly and opens the SQLite database in read-only mode for snapshots and validation. Archive state changes must come from the app-server.
stdinandstdoutmust both be interactive terminals. The command fails immediately without a TTY because project selection is interactive.codexmust be installed and available onPATHwhen--dry-run=false.- Dry-runs do not require the
codexbinary. - If
--dbis omitted, the selected Codex home must contain at least one regularstate_*.sqlitefile. - The selected database must contain the expected
threadstable. --dry-run=falserequires the selected database to be<codex-home>/state_5.sqlite, becausecodex app-serveracceptsCODEX_HOMEbut not a database path.- The output path must be writable.
From the unpacked Release Asset directory:
./thread-archiverFlags:
--codex-home: Codex home directory. Default:~/.codex--db: SQLite database path. When omitted, the command scans--codex-homefor regularstate_*.sqlitefiles. A single discovered database is selected automatically; multiple discovered databases require choosing one.--output: Manifest path. Default:./runs/thread-archiver-<UTC timestamp>.json--dry-run: Record intended archive actions without callingcodex app-server. Default:trueThe selection UI can toggle this mode before the run starts witht.
Example:
./thread-archiver \
--codex-home ~/.codex \
--db ~/.codex/state_5.sqlite \
--dry-run=false \
--output ./runs/thread-archiver-manual.jsonDry-run example:
./thread-archiver --dry-runArchive example:
./thread-archiver --dry-run=falseFor source builds and local development runs, see ../../docs/development.md.
When --db is omitted, the command first lists regular state_*.sqlite files
found directly inside --codex-home. If exactly one database is found, it is
selected automatically. If multiple databases are found, they are shown in a
selectable TUI table; use arrow keys to choose one database and Enter to
confirm.
Before any archive work starts, the command lists all active projects from the
selected database in a selectable TUI table. No projects are selected by
default. Use Space to toggle rows and Enter to confirm. Only the selected
projects' active threads are eligible for the run.
Projects are grouped by normalized cwd.
The active thread source list comes from:
SELECT
id,
rollout_path,
cwd,
source,
updated_at,
title
FROM threads
WHERE archived = 0
ORDER BY COALESCE(updated_at_ms, updated_at * 1000) DESC, id ASC;For older Codex databases without updated_at_ms, the command falls back to
ORDER BY updated_at DESC, id ASC.
After project selection, the command keeps only rows whose normalized cwd
matches one of the selected project paths.
Before starting the app-server, the command validates that selected active root
threads do not have active descendants in unselected projects. If they do, the
run fails before any archive request is sent because thread/archive may
archive descendants as part of the same lifecycle operation.
Before processing each thread, the command re-reads that thread from SQLite. If
the thread is already archived at that point, it is recorded as
skipped_already_archived and no archive request is sent.
With --dry-run, the command does not start the app-server and does not send
thread/archive. Active threads are recorded as dry_run, which means the
thread was eligible to be archived but no archive side effects were requested.
Dry-runs may inspect any selected state_*.sqlite database. Archive mode is
restricted to <codex-home>/state_5.sqlite so the inspected database matches
the database used by codex app-server.
For a thread to be recorded as archived, all of the following must hold:
thread/archivereturned successfully.- The requested thread appears in the collected
thread/archivednotifications. - The source rollout file under
sessions/no longer exists. - The expected rollout file under
archived_sessions/exists. - SQLite shows
archived = 1for that thread after the request.
For a thread to be recorded as skipped_already_archived, all of the following
must hold:
- SQLite already shows
archived = 1before any archive RPC is attempted. - The source rollout file under
sessions/no longer exists. - The expected rollout file under
archived_sessions/exists.
If any validation step fails, the run stops immediately and the manifest is
written with run.status = "failed" and run.fatal_error populated.
The manifest is rewritten incrementally as the run progresses. Its top-level shape is:
{
"run": {},
"summary": {},
"threads_before": [],
"archive_log": [],
"threads_after": []
}Important fields:
run: metadata for the run, including status, timing, paths, and fatal errorsummary: always-present aggregate counts derived fromarchive_logthreads_before: the initial active-thread snapshotarchive_log: one record per initial target threadthreads_after: final archive state for the initial target set
The manifest is an operator-local output file and may contain private metadata:
hostname, absolute local paths, project paths, thread titles, raw source
values, rollout paths, thread IDs, and failure details. Do not publish or share
manifest files without reviewing and redacting them.
Summary fields:
threads_targetthreads_donearchived_countdry_run_countskipped_already_archived_countfailed_count
Per-thread archive statuses:
archiveddry_runskipped_already_archivedfailed
The command always runs in an interactive terminal and uses Bubble Tea for these screens:
- A database picker when
--dbis omitted and multiple databases are found. - A project picker that lists all active projects with thread counts and paths.
Both selection screens let you toggle
dry-runwithtbefore the run starts. - A run view that shows:
- current mode:
archiveordry-run - selected projects
- run status text
- processed-thread counts
- manifest path
- current thread id while a thread is being processed
During execution, Ctrl+C, q, or Esc request cancellation.
When the run finishes or fails, the run view stays open so the final status
remains visible. Close it explicitly with q, Esc, or Ctrl+C.
All screens render to the full terminal height. The help or close hint uses the same footer treatment and stays pinned to the bottom edge.
- The command is sequential. It does not archive threads in parallel.
- The manifest records the selected projects in
run.project_pathsandrun.project_names. run.dry_runin the manifest records whether the run skipped archive RPCs.- A single archive request may also archive descendant threads. Active
descendants in unselected projects are rejected before archiving; selected
descendant notifications observed during that request are included in
archived_thread_ids. - The run stops on the first unexpected error. There is no fallback mode and no silent skipping of failures.