Skip to content

feat(sessions): list Codex conversations, and say why they don't move - #20

Merged
gelzinn merged 2 commits into
mainfrom
feat/codex-sessions
Oct 28, 2026
Merged

gelzinn merged 2 commits into
mainfrom
feat/codex-sessions

Conversation

@gelzinn

@gelzinn gelzinn commented Oct 28, 2026 •

Copy link
Copy Markdown
Owner

Description

seats sessions and seats move only covered Claude. #18 gave Codex a find for herdr and left list out because it wasn't known whether a copied rollout resumes on another account. This answers that from Codex's source (openai/codex at rust-v0.162.1, the installed codex-cli 0.162.1) and a throwaway CODEX_HOME, then builds what the answer allows: Codex conversations are listed, and seats move refuses them with the reason.

What I found

  • codex resume <id> finds a copied rollout. find_thread_path_by_id_str_in_subdir (codex-rs/rollout/src/list.rs:1248-1410) asks the state DB (state_5.sqlite) first, falls back to scanning sessions/ file names when it has no row, and then read-repairs the row into the DB. The id must be a whole UUID (list.rs:1254), so no prefixes.
  • The picker and --last miss a copy until it has been opened once by id. Both query the state DB only and scan files only when the DB returns nothing (tui/src/lib.rs:836-857, tui/src/resume_picker.rs:1509). On a seat that already has history, a copied rollout stays out of them until codex resume <id> indexes it. seats never writes Codex's SQLite (it's versioned, in WAL mode, and a live codex may hold it open).
  • Empirical check, no credentials. In a throwaway CODEX_HOME, after the startup backfill, I added a synthetic rollout. codex exec resume <copied id> started a turn: thread.started with that id, then a 401 from the API because there's no auth. An unknown id failed with no rollout found for thread id …. exec resume --last picked the older indexed thread even though the copy's file was newer. The DB row for the copy appeared after the by-id resume.
  • The rollout records the account, but nothing checks it. creator_user_id and creator_account_id are written at creation (core/src/session/session.rs:1037) and only copied around afterwards.
  • What blocks move: encrypted reasoning. Requests include reasoning.encrypted_content (core/src/client.rs:961), and the reasoning items saved in the rollout are sent back on resume. I found no handling for a server refusing them. A user report shows the API refusing exactly that after an auth switch: invalid_encrypted_content, "organization_id did not match the target organization" (community.openai.com). So a copy is found, but its first turn on another account may be refused. Per the brief, that keeps move refused.
  • Other per-session state:
    • session_index.jsonl holds /rename names (append-only, last entry wins). seats reads it for titles.
    • Paginated threads can point at prefix rollouts through history_base (thread-store/src/local/rollout_lineage.rs), so a copy would need the whole chain. That doesn't matter while move is refused.
    • history.jsonl is the prompt history for up-arrow and isn't needed to resume.

What changed

  • Codex list (src/providers/codex/sessions.ts):
    • Reads rollouts under sessions/ for id, file, cwd, title and mtime.
    • The title is the /rename name, else the first event_msg/user_message. response_item user messages are skipped because they also carry AGENTS.md and the environment context.
    • Only the first 512 KB is read. The session_meta line embeds the base instructions, so it can be longer than the 64 KB headAndTail chunk Claude uses; a test covers a 200 KB meta line.
    • Malformed lines are skipped.
  • The same filters as codex resume.
    • Only cli and vscode sources are listed; codex exec runs and subagents are left out. seats health already runs with --ephemeral, so it adds none.
    • Archived rollouts are left out, as they are in the codex resume listing.
    • A reverted thread (…-<id>_<rollout id>.jsonl) is listed once, from its newest rollout. find now takes the newest rollout for such a thread too.
    • .jsonl.zst rollouts are skipped.
  • SeatSource.sessions.noMove: a reason string. moveSession throws it, after the cross-tool check, without copying anything. Codex sets it; the generic code has no if codex.
  • seats sessions:
    • Codex seats are included (seats sessions codex already filters).
    • Rows whose tool can't move show the whole id, since codex resume takes no prefix; the title column shrinks to make room.
    • Under the table, a hint reads Codex conversations stay on their seat. Reopen one with seats run <seat> resume <id>.
    • The "No Claude seat…" errors now say "No seat with conversations…".
  • Auto-switch for Codex didn't fall out of this and isn't added. One related thing: seats run codex:<seat> resume <id>, the hint above and feat(trust): share folder trust as it's given and resume herdr panes on their seat #18's herdr restore all go through supervise(). With auto-switch on, if that seat is at its limit at launch, it switches to another Codex seat that doesn't have the rollout, and codex resume fails with "no rollout found". This was already the case before this PR; I left it alone because it's in run.ts.
  • README ("Continue a conversation on another seat", the seats sessions line, the herdr note), HELP for seats sessions and seats move, and tests: listing, titles and names, archived, non-interactive, malformed lines, long meta line, reverted threads, the move refusal, the cross-tool refusal and resumeArgs.
  • Verified:
    • bun run check and bun src/cli.ts help.
    • seats sessions --all and seats move <id> codex:personal with a throwaway HOME, using the rollouts the real codex 0.162.1 had rewritten in the check above. The listing and hint print, the move exits 1 with the reason, and nothing is written to the target seat.
    • 800 synthetic rollouts of about 6 MB each list in about 75 ms.

Open

  • The refusal rests on one report, which was a ChatGPT login switched to an API key (an org change), plus the code path. I didn't test two ChatGPT accounts, and seats in the same workspace may well work. To settle it: copy one rollout between two real seats at the same relative path, run CODEX_HOME=<other seat> codex resume <id>, and send a prompt. If it works across accounts, dropping noMove enables move; that would also need history_base prefixes copied for paginated threads.

User-facing changes

BEGIN_COMMIT_OVERRIDE
feat(sessions): seats sessions lists Codex conversations too, with the command to reopen one on its seat
END_COMMIT_OVERRIDE

Type of change

  • Bug fix
  • New provider
  • New feature
  • Docs / metadata

Checklist

  • bun run check passes (Biome lint and format, typecheck, tests)
  • bun src/cli.ts help runs without errors
  • Changes to seat folders were tried with --dry-run or a throwaway HOME
  • A new provider (if added) lives in src/providers/<id>/ and is listed in src/providers/index.ts

@gelzinn
gelzinn force-pushed the feat/codex-sessions branch from 2736a0d to 5c8f255 Compare October 28, 2026 15:43
@gelzinn
gelzinn changed the base branch from feat/session-hook to main October 28, 2026 15:44
@gelzinn gelzinn self-assigned this Oct 28, 2026
seats sessions now lists Codex seats' conversations next to Claude's, the
same ones codex resume lists there: archived ones, codex exec runs and
subagents stay out. The title is the name given with /rename, else the
first prompt, and the Id column shows the whole id, which codex resume
needs.

seats move refuses a Codex conversation and says why: Codex finds a copied
rollout by its file name, but the conversation keeps reasoning encrypted for
the account that ran it, which another account can refuse. The list points
at seats run codex:<seat> resume <id> instead.
The hint under seats sessions now reads seats run <seat> resume <id>, so the
Seat column's value goes in as it is, and the help for seats move says it
copies Claude conversations.
@gelzinn
gelzinn force-pushed the feat/codex-sessions branch from 5c8f255 to 05b5ca1 Compare October 28, 2026 16:21
@gelzinn
gelzinn merged commit 6a95a26 into main Oct 28, 2026
6 of 7 checks passed
@gelzinn
gelzinn deleted the feat/codex-sessions branch October 28, 2026 17:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant