Skip to content

Latest commit

 

History

History
244 lines (188 loc) · 8.42 KB

File metadata and controls

244 lines (188 loc) · 8.42 KB

thread-archiver

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.

What It Does

On an archive run, the command:

  1. Uses --db when provided; otherwise scans the selected Codex home for regular state_*.sqlite files. One discovered database is selected automatically; multiple discovered databases are shown in a single-choice picker.
  2. Reads the initial set of active threads from the selected database file.
  3. Groups those threads by project, where project identity is the normalized cwd value.
  4. Shows an interactive Bubble Tea project picker with no projects selected.
  5. Keeps only the explicitly selected projects.
  6. Starts codex app-server --listen stdio://.
  7. Performs the required initialize and initialized handshake.
  8. Archives threads one at a time with thread/archive.
  9. Verifies that each archive actually took effect.
  10. 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.

Requirements

  • stdin and stdout must both be interactive terminals. The command fails immediately without a TTY because project selection is interactive.
  • codex must be installed and available on PATH when --dry-run=false.
  • Dry-runs do not require the codex binary.
  • If --db is omitted, the selected Codex home must contain at least one regular state_*.sqlite file.
  • The selected database must contain the expected threads table.
  • --dry-run=false requires the selected database to be <codex-home>/state_5.sqlite, because codex app-server accepts CODEX_HOME but not a database path.
  • The output path must be writable.

Usage

From the unpacked Release Asset directory:

./thread-archiver

Flags:

  • --codex-home: Codex home directory. Default: ~/.codex
  • --db: SQLite database path. When omitted, the command scans --codex-home for regular state_*.sqlite files. 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 calling codex app-server. Default: true The selection UI can toggle this mode before the run starts with t.

Example:

./thread-archiver \
  --codex-home ~/.codex \
  --db ~/.codex/state_5.sqlite \
  --dry-run=false \
  --output ./runs/thread-archiver-manual.json

Dry-run example:

./thread-archiver --dry-run

Archive example:

./thread-archiver --dry-run=false

For source builds and local development runs, see ../../docs/development.md.

Selection Rules

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.

Validation Rules

For a thread to be recorded as archived, all of the following must hold:

  • thread/archive returned successfully.
  • The requested thread appears in the collected thread/archived notifications.
  • The source rollout file under sessions/ no longer exists.
  • The expected rollout file under archived_sessions/ exists.
  • SQLite shows archived = 1 for 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 = 1 before 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.

Output Manifest

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 error
  • summary: always-present aggregate counts derived from archive_log
  • threads_before: the initial active-thread snapshot
  • archive_log: one record per initial target thread
  • threads_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_target
  • threads_done
  • archived_count
  • dry_run_count
  • skipped_already_archived_count
  • failed_count

Per-thread archive statuses:

  • archived
  • dry_run
  • skipped_already_archived
  • failed

TUI Behavior

The command always runs in an interactive terminal and uses Bubble Tea for these screens:

  1. A database picker when --db is omitted and multiple databases are found.
  2. A project picker that lists all active projects with thread counts and paths. Both selection screens let you toggle dry-run with t before the run starts.
  3. A run view that shows:
  • current mode: archive or dry-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.

Notes

  • The command is sequential. It does not archive threads in parallel.
  • The manifest records the selected projects in run.project_paths and run.project_names.
  • run.dry_run in 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.