codex-cli¶
OpenAI's Codex CLI (catalog agent codex-cli), the Rust codex-rs workspace, writes one
append-only JSONL transcript per thread, which Codex calls a rollout, under ~/.codex: each line an
envelope around a tagged item, plus a global prompt history. The same code runs behind the VS Code
extension and, as Codex Desktop, inside the ChatGPT desktop app (see
chatgpt-desktop.md); the standalone CLI and the desktop app share one
~/.codex and one login. Open Interpreter is a fork that keeps the format byte for byte under
~/.openinterpreter (open-interpreter.md, which records only what
differs), and Ollama's ollama launch writes files into ~/.codex
(Files written by Ollama). The agent is parsed by
doubleagent/parsers/codex.py (CodexParser, built on the shared RolloutParser). The TUI is the
CLI's terminal user interface.
Source and evidence¶
openai/codex, Apache-2.0, open source, shallow clone at
scratchpad/repos/codex, HEAD
3e238776e857eccd3bde6bff3026e2e9798f6524
(2026-10-03). Paths below are relative to codex-rs/. All claims are source (file:line under
codex-rs/) unless marked local (the names-only listing of ~/.codex on the development host
under Observed), observed (the real installs under Observed) or
web (community write-ups, used only for the desktop app); every schema claim is from source
unless it is marked local or observed. A citation at the later commit
4e7b9e2 marks a
fact found or changed after 3e23877 (Source reviews).
Observed¶
- macOS, Codex Desktop inside ChatGPT 26.1013.40934
(issue 49, the ChatGPT desktop parser,
issue 149, and
issue 160, the resume and sync question):
ChatGPT
26.1013.40934(bundle identifiercom.openai.codex, the build after the 26.1007.71326 of the 2026-10-15 round) with the bundled Codex0.163.0-alpha.10.2(originatorCodex Desktop), from 2026-10-18 18:15Z to 2026-10-19 00:43Z, in the macOS lab VM of the 2026-10-15 bullet below (macOS 26.6.2, build 25G83, arm64, one user). The user performed one step at a time and a listing of names, sizes and modification times was diffed after each, so each file is mapped to the step that wrote it; files were read for names, sizes, key names, value types, enum values and row counts only. Times are UTC; the listings of the first five steps were taken in the VM's local zone, Eastern Daylight Time (UTC-4), and converted. Every thread was started in the composer's Codex mode (the composer is the app's prompt box; its Codex mode starts a local Codex thread with a rollout, its ChatGPT mode lists the account's chatgpt.com conversations). The steps: install (first launch 18:17:26Z) and sign-in (18:31:28Z to 18:32:34Z), which addedauth.json,models_cache.jsonand the first shell snapshot to a~/.codexthat already held the databases and the global state (macOS, ChatGPT 26.1013.40934); a plain question with no tools (18:40:09Z to 18:40:31Z), whosecwdwas the app's projectless output directory (the directory the app creates under~/Documents/Codex/<date>/for a thread that belongs to no project)~/Documents/Codex/2026-10-18/w(Scratch directories) and whose prompt went to the global state, not tohistory.jsonl; the project~/Demoadded through the folder picker (18:48:52Z), which wrote a trusted-project table toconfig.toml, and a coding task in it (18:49:06Z to 18:50:51Z) that wrote three files there and nothing else in the project; in that thread, a tool call the user declined and one the user approved (19:03:50Z to 19:05:03Z), whose decisions are only inlogs_2.sqlite; a model switch (21:25:09Z), the Approve for me mode (21:25:28Z), in which an automatic reviewer the app calls the guardian answers approval requests instead of the user, and a prompt (21:26:40Z to 21:27:32Z) whose write the reviewer allowed in a thread of its own (Guardian review threads), the file going to~/Documentsrather than the Desktop the prompt named; a quit (about 21:38Z), relaunch (21:45:36Z) and resume of the Demo thread (21:46:06Z) with one prompt (21:47:36Z to 21:47:45Z), which appended to the same rollout (Joins and timestamps); a turn in Full access, the access level that runs commands with no sandbox and no approvals (set 21:52:30Z, turn 21:53:27Z, set back by hand at 21:53:38Z); a reply in ChatGPT mode to an existing ChatGPT conversation (about 22:02Z to 22:15Z), which wrote nothing under~/.codex(Transcript stores inchatgpt-desktop.md); a scheduled task of the kind the app calls a heartbeat (each run ends with the model deciding whether to notify the user), created at 23:18:38Z, run with Run now (23:19:26Z to 23:19:30Z) and once on schedule (23:45:49Z to 23:45:53Z) (Automations); remote control, which pairs a phone with the desktop's app server (Codex's local service that owns the threads), enabled at 23:50:17Z, with a prompt from an iPhone (23:59:14Z to 23:59:31Z), connections turned off (00:05:42Z) and on (00:09:25Z), the iPhone revoked (00:14:36Z), an Android phone paired (00:23:10Z) and a prompt from it (00:29:14Z to 00:29:16Z) (state_5.sqliteremote control enrollments); and the plain question's thread deleted with the app's own command at 00:36:02Z (What survives a delete). The rollouts are in Observed rollouts and the record shapes they showed in Record schema and Other stores. Exercised here for the first time on any platform: a resumed thread, a denied and an approved tool call, a model switch within a thread, an automatic approval, scheduled-task runs, remote control and a delete. Not exercised: an "always allow" grant (the dialog of build 26.1013.40934 offered none for a command), a subagent the agent spawned itself, a heartbeatNOTIFYdecision, anautomation_runsrow and an archived thread. - Ubuntu, standalone CLI (issue 113 and
issue 118): Ubuntu 26.04.1 LTS, Codex CLI
0.162.1 standalone
(
codex --versionprintscodex-cli 0.162.1), 2026-10-15, 17:12Z to 17:22Z (UTC), in the author's Ubuntu lab VM (aarch64, kernel 7.0.0-38-generic, one user; no Node.js, npm, snap or deb package of Codex, and no ChatGPT app, which has no Linux build), read for file names, sizes, key names, value types and line counts only. The CLI was installed with its shell installer, logged in through the browser flow, which wroteauth.jsonandlog/codex-login.log(Credentials), and given two turns in~/demo, which wrote seven files there, one rollout (Observed rollouts) and twohistory.jsonllines. The tree holds none of the desktop app's files (Standalone CLI on Linux). - macOS, Codex Desktop inside ChatGPT 26.1007.71326 and standalone CLI
(issue 113,
issue 118,
issue 120 and
issue 121): ChatGPT
26.1007.71326(bundle idcom.openai.codex; the app's build is in Source and evidence inchatgpt-desktop.md) with the bundled Codex0.162.0-alpha.17.2(originatorCodex Desktop), then Codex CLI0.162.1(originatorcodex-tui) from its shell installer, 2026-10-15 (16:25Z to 17:05Z, UTC), in the author's macOS lab VM, macOS 26.6.2 build 25G83, arm64, one user. The user repeated the Windows round with no listing between tasks; the files were read for file names, sizes, key names, value types and row counts only. The round wrote four rollouts (Observed rollouts): a project task in~/Demo(16:39Z to 16:40Z); the target thread of a scheduled automation created at 16:44Z, in~/Documents/Codex/2026-10-15/new-chat(Automations); a thread in the projectless output directory~/Documents/Codex/2026-10-15/e(16:50Z, with acodex_app.load_workspace_dependenciestool call; which user action it was is not determined); and the CLI's one prompt in~/demo2(16:55Z to 16:56Z), which the CLI also wrote tohistory.jsonl. A plain chat and an image generation left no rollout (Transcript stores inchatgpt-desktop.md). The~/.codextree is the Windows one with the differences in macOS differences. - Windows, standalone CLI (issue 113 and
issue 120): Codex CLI
0.162.1(originatorcodex-tui), installed at 15:38Z on 2026-10-15 (run until 15:46Z) in the VM of the Codex Desktop bullet below, beside the ChatGPT desktop app, with the PowerShell installer OpenAI documents for Windows (the exact command was not recorded). No npm, Node.js, winget or scoop is on the host. The user gave it one prompt from a PowerShell console in a new folder,C:\users\<user>\demo-cli(~\demo-cli, typed in lower case). The CLI shared the desktop app's~\.codexand its login:auth.jsonwas not rewritten, no login was asked for and no second credential file appeared. It wrote one rollout, whosesourceisvscodelike the desktop app's (Observed rollouts, Joins and timestamps), onehistory.jsonlline, which the desktop app had not written, and the files in Standalone CLI on Windows. The sandbox failed to start a process during the run (.sandbox/setup_error.jsoncodehelper_unknown_error; the daemon's stderr log records a failed process creation and a failed file write in the project folder), so some tool calls failed; what the folder ended up holding was not surveyed. The console's own command history is in PSReadLine'sConsoleHost_history.txt, which is shell history, the job of the EDR (the endpoint detection and response tool), and not collected. - Windows, Codex Desktop inside ChatGPT 26.1007.9446.0
(issue 113,
issue 114,
issue 115,
issue 116 and
issue 118): the
ChatGPT desktop app
26.1007.9446.0(the Microsoft Store packageOpenAI.Codex, an MSIX package, the Windows app package format; seechatgpt-desktop.md) bundles Codex, which identifies itself in its rollouts asoriginatorCodex Desktop,cli_version0.162.0-alpha.17.2; the desktop app's own database recordsapp_version26.1007.71326. Observed 2026-10-15 in the author's Parallels lab VM, Windows 11 ARM64 (the 64-bit Arm architecture;verreports10.0.26300.9550), one user, times in UTC, read for file names, sizes, key names, value types, enum values and row counts only; no listing was taken between tasks, so the mapping from task to file is by modification time only. The bundled Codex uses the standardCODEX_HOME,~\.codex(~is%USERPROFILE%), created at first launch (Codex Desktop inside the ChatGPT app). The user ran two local threads, one in the app's default output directory for a thread with no project,~\Documents\Codex\2026-10-15\d(14:26:37Z to 14:27:19Z), and one in a project folder on the Desktop (14:30:05Z to 14:32:47Z), which wrote eleven files there; a ChatGPT chat with an image generation, which left no rollout (Transcript stores inchatgpt-desktop.md); and a first attempt at a scheduled automation, which failed in the app and wrote nothing. Later the same day (survey at 15:30Z) the user subscribed the account to ChatGPT Plus (the paid plan), which only refreshed the token inauth.jsonand grewmodels_cache.jsonfrom 5 to 9 models; created a scheduled automation that saved, which wroteautomations/and at once a third rollout, its target thread (Automations); and connected a GitHub account, which wrote nothing locally (the connection is held by the ChatGPT account on the server). The rollouts are in Observed rollouts. Not exercised: a resumed thread, a denied tool call, an "allow always" grant, a model switch within a thread, subagents, an automation run; the macOS round of 2026-10-18 exercised all of them but the grant and a subagent the agent spawned itself. - Local: the real install on the development host, Codex 0.160.0 (
session_meta.cli_version), one rollout of 20 lines, a 1-linehistory.jsonland a 1-linesession_index.jsonl, plus scratchpad copies ofstate_5.sqliteandthread_history_1.sqlite. Only record types, key paths, value types and enum values were tabulated; no content was read into this document. The one rollout ishistory_mode: "paginated"and has nogitblock, so the legacy event set andgitare source only. The databases were opened only from scratchpad copies, and only for table names, row counts and key names.
Source reviews¶
- 2026-10-21: openai/codex at
4e7b9e2, the commit of the 2026-10-19 review; source only. The parser now reads the standing grants, thePatchApprovallog lines, theguardian_review_feedbackrecords, thethread_goal_updatedevents, the memory rollout summaries and thedb-backups/copies ofstate_5.sqlite, and project discovery readsarchived_sessions/and skips the thread worktrees; the review corrected thethread_goal_updatedkey names to camelCase, located the network policy inpermission_profile(Format versions) and added the Source host rows. - 2026-10-19: openai/codex at
4e7b9e2, the head ofmainthat day, diffed file by file against3e23877; source only. It added the standing grants and per-call approval records (Standing grants and approval records), the memories, goals and backup stores (Other past-use stores), the thread worktrees as scratch directories (Scratch directories) and the reach ofthread/delete(What survives a delete), and recorded the format drift in Format versions, which the parser handles.
Collection¶
Per-user storage¶
Home resolution: CODEX_HOME if set (must exist, canonicalised) else dirs::home_dir()/.codex
(utils/home-dir/src/lib.rs:13-56).
No OS branching: Linux, macOS and Windows all use ~/.codex / %USERPROFILE%\.codex. The macOS
desktop app uses the same ~/.codex for sessions (web:
openai/codex issue 49797, jazzyalex write-up) and
keeps only Sparkle update state in ~/Library/Application Support/com.openai.codex
(cli/src/doctor/updates.rs:586).
Since 26.1007 the bundle id com.openai.codex is the ChatGPT app, which bundles Codex (observed on
macOS, 2026-10-15), so that directory is collected as chatgpt-desktop
(issue 121).
Inside $CODEX_HOME:
sessions/YYYY/MM/DD/rollout-<YYYY-MM-DDThh-mm-ss>-<thread-uuid>.jsonltranscripts (rollout/src/lib.rs:86, rollout_file_name.rs:51-67, recorder.rs:1733-1734, list.rs:905-916); may be zstd-compressed.jsonl.zst(rollout/src/compression.rs:29,68,98).archived_sessions/same layout (rollout/src/lib.rs:87).session_index.jsonl(rollout/src/session_index.rs:20). Local confirmssessions/2026/10/03/rollout-*.jsonl.history.jsonlprompt history (message-history/src/lib.rs:52,86).- SQLite:
state_5.sqlite(threads, metadata),logs_2.sqlite,goals_1.sqlite,memories_1.sqlite,queue_1.sqlite,thread_history_1.sqlite(state/src/sqlite.rs:34-39),agent_message_board_1.sqlite(ext/agent-message-board/src/local.rs:55; at4e7b9e2inext/agent-message-board/src/local/pool.rs, 16, same file name), with-wal/-shmsidecars (local). Recovery copies indb-backups/sqlite-<n>/(state/src/runtime/recovery.rs:16). memories/(MEMORY.md,rollout_summaries/*.md),memories_v2/,memories_extensions/(memories/read/src/lib.rs:14, memories/write/src/control.rs:5-7,56-63). At4e7b9e2the memories pipeline, off by default, also writesmemories/raw_memories.mdandmemories/memory_summary.mdand the databasesmemories_1.sqliteandmemories_v2_1.sqlite(state/src/sqlite.rs:43); what they hold is in Other past-use stores. Not seen on a real install. Collected.log/(codex-tui.log,codex-login.log) (core/src/config/mod.rs:4093, tui/src/lib.rs:281, cli/src/login.rs:85). Daemon logs inapp-server-daemon/(feedback/src/daemon_logs.rs:18);app-server-control/socket dir (app-server-transport/src/transport/mod.rs:53).shell_snapshots/(core/src/shell_snapshot.rs:107),skills/with bundledskills/.system/(skills/src/lib.rs:57),plugins/andplugins/cache/(core-plugin-common/src/installed.rs:11),packages/managed binaries (app-server-daemon/src/managed_install.rs:52),worktrees/(worktree/src/settings.rs:44),visualizations/(tui/src/inline_visualization.rs:93),.tmp/(rollout/src/maintenance.rs:27, core-plugins/src/startup_sync.rs:171;.tmp/bundled-marketplaces/),tmp/arg0/(arg0/src/lib.rs:363),cache/(tui/src/pets/ambient.rs:158),installation_id(core/src/installation_id.rs:17),version.json(cli/src/doctor/updates.rs:35),.sandbox_migration(execpolicy/src/sandbox_migration.rs:10),config.toml(config/src/lib.rs:52),rules/exec policy (core/src/exec_policy.rs:54),hooks.json,agents/.- Windows sandbox:
.sandbox/(logs.sandbox/logs/sandbox.<date>.log,setup_error.json),.sandbox-bin/,.sandbox-secrets/(windows-sandbox-rs/src/spawn_prep.rs:108, logging.rs:240-251, setup.rs:196, uninstall_windows.rs:132-137). - Local only (not grepped in source):
models_cache.json,thread-writer-locks/,tui-thread-reference-capabilities/,.sqlite-maintenance.lock; the first two are described under Codex Desktop inside the ChatGPT app.
Scratch directories¶
Documents/Codex/<date>/<slug>/{work,outputs}(~\Documents\Codex\<date>\<slug>\on Windows): the ChatGPT desktop app's projectless output directory, the working directory the bundled Codex is given for a thread with no project, which the thread's rolloutcwdnames;outputs/holds the files the agent wrote for the user. Collected whole by thecodex-cli|Documents/Codexcatalog line (issue 149); project discovery drops a rolloutcwdin it (Where the project path is recorded). Observed on Windows (ChatGPT 26.1007.9446.0, 2026-10-15; Per-user storage inchatgpt-desktop.md) and on macOS (2026-10-15 and 2026-10-18; Observed); not seen on the Ubuntu install, which ran the standalone CLI only. The Windows install has not yet had a per-step round, so which action creates the directory is known only from the macOS listings.<CODEX_HOME>/worktrees/<first 4 hex of a uuid>/<repository name>(.codex/worktrees/...by default): the git worktrees Codex creates for a thread, unlessconfig.toml[desktop] git-worktree-rootnames another absolute path (source at4e7b9e2, not seen on a real install) (worktree/src/settings.rs:12-13, 43-56, worktree/src/paths.rs:13-30);[desktop] worktree-auto-cleanup-enabled, on by default, lets Codex remove them (settings.rs:62-70). The default location is under the.codex/worktreesexclusion, so its files are not collected. Project discovery drops a rolloutcwdunder<home>/.codex/worktreesas it drops one underDocuments/Codex(find_cli_session_projects, collector 1.18.0, issue 222); a thread that ran under a configured root outside the home still has that worktree discovered as a project and its project files collected. The analyzer classes a worktree under.codexas a scratch directory and one under a configured root as a project. The thread that owns a worktree is incodex-thread.json(version,ownerThreadId) inside the source repository's per-worktree git directory (worktree/src/metadata.rs:19-27, 88-91), which no catalog line names.
Files written by Ollama¶
Written by Ollama, not by Codex (Ollama source at v0.35.1, commit
b0c1ca4f7549d7acdfa52a7dcffc934bc63a43ce;
detail in ollama.md, Project-local files). Ollama
always uses <home>/.codex and ignores CODEX_HOME
(cmd/launch/codex.go:234-240).
ollama launch chatgpt(the ChatGPT desktop app's Codex, macOS and Windows) writesollama-launch-codex-routing.jsonandollama-launch-models.json(internal/proxy/codex_desktop.go:32-38,cmd/launch/codex_app.go:952-958), rewritesconfig.toml(rootmodel,model_catalog_json,openai_base_urlpointing at<ollama host>/api/codex/v1;profileandmodel_providerremoved;[desktop]reasoning efforts extended,codex_app.go:312-354), and createsauth.jsonwhen none exists (Credentials). The previousconfig.tomland catalogs are copied to~/.ollama/backup/codex-app/, which theollamacatalog line collects.ollama launch codex(the CLI) writesmodel.jsonandollama-launch.config.tomland leavesconfig.tomlalone (codex.go:209-232,250-268).
All of these sit under .codex and are collected by the codex-cli|.codex line; none is excluded.
Codex Desktop inside the ChatGPT app¶
Observed on the Windows install of Source and evidence (ChatGPT
26.1007.9446.0, 2026-10-15), not in the source at
3e23877 unless a
citation says otherwise. Names, types and counts only; no value from the install is recorded.
~\.codex held 9,155 files, most of them plugin and skill copies under .tmp\, plugins\cache\
and skills\.system\. The desktop app adds:
.desktop-created(0 B, written at first launch) and.codex-provisioning-<16 hex>.guard(0 B): markers that the desktop app created and provisioned this home.installation_id(36 B),.sandbox_migration(3 B),.sqlite-maintenance.lock(0 B, byte-range locked while the app runs)..codex-global-state.jsonand.codex-global-state.json.bak(61,390 B each, 123,250 B after a Plus subscription and an automation): the desktop app's interface and account state, with the user's prompts, each thread's standing permissions and ChatGPT conversation resume tokens (.codex-global-state.json). Flagged secret (Credentials).sqlite/codex-dev.dbwith a WAL sidecar (write-ahead log, where SQLite holds recent writes): the desktop app's own thread catalog and automation store (sqlite/codex-dev.db). Collected.state_5.sqlite,thread_history_1.sqliteandlogs_2.sqlite, the source-described stores, with the tables and columns this build adds (Other stores).plugins/.plugin-appserver/(codex.exe, 282,678,064 B, andcodex-code-mode-host.exe,codex-command-runner.exe,codex-windows-sandbox-setup.exe, 7 to 70 MB): copies of the package's binaries, excluded (issue 116).plugins/.remote-plugin-install-staging/(empty) is excluded with it..tmp/bundled-marketplaces/openai-bundled/(.materialization-key,plugins/, 274 MB) and.tmp/plugins/(60 plugin directories, 57 MB; the whole.tmp/was gone by the second survey, so it is a temporary tree), the marketplace sync the source cites (core-plugins/src/startup_sync.rs:171);cache/remote_plugin_catalog/<16 hex>.json(36 MB),cache/codex_apps_tools/andcache/codex_apps_server_info/;plugins/cache/withopenai-bundled,openai-curated-remoteandopenai-primary-runtime(149 MB). All under existing exclusions.automations/(after a scheduled automation was created in the app):.run-jitter-salt(37 B) and one directory per automation, named by a slug made from the name the user typed (user content), holdingautomation.toml(383 B) (Automations). Collected.ambient-suggestions/<40 hex>/ambient-suggestions.json(139 B): the app's suggested tasks for a project root (Ambient suggestions). Collected.computer-use/config.json(152 B:accentColor,direction,locale,strings),vendor_imports/skills-curated-cache.json(1,267 B:fetchedAt,skills),models_cache.json(177,581 B:client_version,etag,fetched_at,identity,modelswith each model's instructions template and confirmation policies for browser and computer use): collected.thread-writer-locks/(.coordination.lockand one<thread uuid>.lockper thread, 0 B, byte-range locked while the app runs; Thread locks and shell snapshots),project-metadata-locks/(empty),visualizations/<yyyy>/<mm>/<dd>/<thread uuid>/(empty, excluded),code-review-plugin/andpets/(empty)..sandbox/(deny_read_acl_state.json,sandbox.<date>.log,sandbox-<16 hex>.log,setup_marker.json, excluded),.sandbox-bin/(empty, excluded) and.sandbox-secrets/sandbox_users.json(Credentials), andtmp/arg0/codex-arg0<6 chars>/(apply_patch.bat,applypatch.bat, excluded).config.tomlas the app writes it:notify, a[desktop]table (followUpQueueMode,conversationDetailMode,ambient-suggestions-enabled),marketplaces.openai-bundledandmarketplaces.openai-primary-runtime(source_typelocal),plugins.<id>@openai-bundled.enabledandplugins.<id>@openai-primary-runtime.enabledentries, anmcp_servers.node_replserver with itsenv, andwindows.sandboxelevated.
The Windows sandbox, which the source describes
(setup.rs:196),
was set up during onboarding: it created two enabled local accounts, CodexSandboxOffline and
CodexSandboxOnline, whose passwords are in .sandbox-secrets/sandbox_users.json, and the package
runs a service CodexSandboxService.OpenAI.Codex. The accounts and the service are the EDR's view
and are not collected.
Outside ~/.codex the app keeps copies of the binaries in %LOCALAPPDATA%\OpenAI\Codex and a
1.27 GB runtime in ~/.cache/codex-runtimes, neither collected; see Exclusions in
chatgpt-desktop.md.
Standalone CLI on Windows¶
Observed with Codex CLI 0.162.1 from the PowerShell installer (Source and evidence); names, types and sizes only.
~\.codex\packages\standalone\:install.lock(0 B),auto-update-version(31 B, one token),current(an NTFS junction, a directory reparse point, toreleases\0.162.1-aarch64-pc-windows-msvc) and that release:bin\codex.exe(282,434,864 B),bin\codex-code-mode-host.exe(69,820,208 B),codex-package.json(216 B:entrypoint,layoutVersion,pathDir,resourcesDir,target,variant,version),codex-path\rg.exe, andcodex-resources\withcodex-command-runner.exe,codex-windows-sandbox-setup.exeandvoice\(bin\codex-voice-host.exewith GStreamer, GLib, Opus and PCRE2 libraries,manifest.json,runtime.json,sources.json,windows-crt.json, licences). File times inside the release are the build's (2026-10-09). Thepackages/directory the source names (app-server-daemon/src/managed_install.rs:52).~\.codex\packages\app-server-daemon\: the same layout with the same binaries, fetched by the daemon's updater.%LOCALAPPDATA%\Programs\OpenAI\Codex\bin: a junction to~\.codex\packages\standalone\current\bin, which the installer adds to the userPathvalue inHKCU\Environment(the current user's environment variables in the registry). No Start Menu entry and no Uninstall registry key.~\.codex\app-server-daemon\: the CLI runs the app server as a daemon that outlives the TUI.daemon.lock,daemon.pid.lock,daemon-updater.pid.lock(0 B),daemon.pid(328 B, JSON:executableIdentity.{digest, path_digest}as arrays of 32 numbers,pid,processStartTime),daemon-updater.pid(52 B:pid,processStartTime),daemon.stderr.log(Rusttracinglines with ANSI colour codes,<ISO 8601 time> ERROR codex_core::tools::router: error=...),daemon-updater.stderr.log(JSON lines withtimestampMs,processPid,stage(installer_fetch,installer_run),outcome,elapsedMs,failure, oreventpackage_selectedwithdetails.{release, trigger}),daemon-updater.sockand an emptyworkdir\.~\.codex\app-server-control\holdsapp-server-control.sockandapp-server-startup.lock(0 B). The two.sockfiles are Unix domain sockets.cap_sid(419 B, JSON):readonly,workspace,workspace_by_cwd(a map from ac:\...path to a string) andwritable_root_by_path(twoc:\...paths to strings): capability SIDs by workspace path (SIDs are Windows security identifiers; a capability SID has the formS-1-15-3-...), inferred from the key names and value lengths, not from a value. Collected, not flagged.version.json(105 B:dismissed_version,last_checked_at,latest_version), the CLI's update check, cited in the source list above;history.jsonl(one line:session_id,text,ts), the CLI's prompt history, which the desktop app had not written;tui-thread-reference-capabilities\<thread uuid>(0 B, one per TUI thread);node_repl\active_execs\(empty), the scratch of thenode_replMCP server thatconfig.tomlconfigures (MCP is the Model Context Protocol, through which an agent calls external tools)..sandbox\gainedsetup_error.json(77 B:code,message) and a longersandbox.<date>.log;.sandbox-bin\gained the sandbox's own copies ofcodex.exeandcodex-command-runner-0.162.0-alpha.17.2.exe(289,784,416 B together).config.tomlwas rewritten at install (43 B longer; its keys were not re-read).auth.json,.codex-global-state.jsonand the desktop app's automation were not changed by the CLI.
macOS differences¶
Observed on the macOS install of Source and evidence; names, types and sizes
only. Everything else in
Codex Desktop inside the ChatGPT app and
Standalone CLI on Windows was present in the same form (auth.json,
config.toml, .codex-global-state.json, the SQLite stores, sqlite/codex-dev.db, automations/,
ambient-suggestions/, vendor_imports, history.jsonl, version.json, app-server-daemon/,
app-server-control/).
plugins/.plugin-appserver/codex-cli/(337,674,240 B): on macOS the copied binaries sit in acodex-clisubdirectory; the.codex/plugins/.plugin-appserverexclusion covers it.computer-use/Codex Computer Use.app(bundle idcom.openai.sky.CUAService, version 26.1005.1001425, 168 files, 76,615,353 B), the computer-use agent's app, besidecomputer-use/config.json, excluded (Exclusions).ipc/ipc.sock(a Unix domain socket) and an emptyshell_snapshots/(2026-10-15; on 2026-10-18 it held the two most recent snapshots of each live thread, see macOS, ChatGPT 26.1013.40934), macOS only.- No
.sandbox,.sandbox-binor.sandbox-secrets: the Windows sandbox accounts have no macOS counterpart. The macOS sandbox is Seatbelt (Apple's sandbox profile mechanism), which left no file that was observed..sandbox_migrationis present. app-server-daemon/daemon.pid(472 B) anddaemon-updater.pid(194 B) carry aprocessIdentityobject (bootId, a uuid,startSeconds,startMicroseconds,uniqueId) that the Windows files lack.app-server-control/app-server-control.sockis a real symlink here, to/private/tmp/codex-daemon-<uid>/<64 hex>;app-server-daemon/daemon-updater.sockandipc/ipc.sockare sockets.- The standalone CLI:
packages/standalone/releases/0.162.1-aarch64-apple-darwin/andpackages/app-server-daemon/releases/0.162.1-aarch64-apple-darwin/(bin/,codex-path/,codex-resources/,codex,codex-package.json), each with acurrentsymlink, 676,267,646 B in all, under the.codex/packagesexclusion. The installer adds the symlinks~/.local/bin/codexand~/.local/bin/codex-code-mode-hostintopackages/standalone/current/bin, which no catalog line names, and a block in~/.zprofilebetween# >>> Codex installer >>>and# <<< Codex installer <<<that prepends~/.local/bintoPATH; shell startup files are not collected. The daemon processes ran from theapp-server-daemonrelease.
macOS, ChatGPT 26.1013.40934¶
Observed on the macOS install of 2026-10-18 (Source and evidence), bundled
Codex 0.163.0-alpha.10.2; names, types and sizes only. Everything in
Codex Desktop inside the ChatGPT app and
macOS differences was present, with these additions and observations:
- Before sign-in
~/.codexalready held.codex-global-state.json(1,282 B),config.toml(2,114 B),installation_id(36 B),.sqlite-maintenance.lock,ipc/ipc.sock,tmp/arg0-macos/codex-arg0*/,vendor_imports/skills-curated-cache.json,.tmp/,plugins/,skills/.system/,computer-use/Codex Computer Use.app,sqlite/codex-dev.db,state_5.sqlite(every table empty butthread_sections, 1 row, and the bookkeeping tables),logs_2.sqlite,goals_1.sqlite,memories_1.sqliteandqueue_1.sqlite; noauth.json,sessions/orhistory.jsonl. Sign-in wroteauth.json(4,299 B),models_cache.json(399,712 B, 9 models),.sandbox_migration(3 B), the first shell snapshot and the firstambient-suggestionsfile, and grew the global state to 110,327 B andconfig.tomlto 2,380 B. Nohistory.jsonlorversion.jsonwas written all round: the desktop app keeps prompts in the global state'sprompt-history.thread_history_1.sqlitewas created with the first thread, andcomputer-use/config.json(152 B) with it. shell_snapshots/<thread uuid>.<unix ns>.sh(2,135 to 2,299 B): the user's login-shell environment replayed for the sandbox, written by the source's core/src/shell_snapshot.rs:107 (Thread locks and shell snapshots). Flagged secret (Credentials).thread-writer-locks/<uuid>.lock, also for prewarmed reviewer sessions that never wrote anything (Thread locks and shell snapshots).automations/.run-jitter-salt(37 B) andautomations/<slug>/automation.toml(356 B; the slug is the kebab-case of the task's name) (Automations).- A second
ambient-suggestions/<40 hex>/directory when~/Demowas added, one per project root (Ambient suggestions). cache/remote_plugin_catalog/<16 hex>.json(38,747,166 B, downloaded at each launch),cache/codex_apps_server_info/<40 hex>.json(189 B) andcache/codex_apps_tools/<40 hex>.json(825,347 B): under the excluded.codex/cache, which was 39.6 MB at collection.config.tomlgains a[projects."<path>"]table withtrust_leveltrustedwhen a project folder is added (Standing grants and approval records). Neither the model nor the permission mode (Approve for me, Full access) is written to it.state_5.sqliteon this build also hasguardian_review_feedback,external_agent_config_imports,thread_spawn_edgesandthread_read_receipts, and oneremote_control_enrollmentsrow once remote control is enabled (state_5.sqliteremote control enrollments)..codex-global-state.jsongrew from 1,282 B before sign-in to 120,989 B by the remote-control step (.codex-global-state.json).- Outside
~/.codex:~/.cache/codex-runtimes/codex-primary-runtime/(32,625 entries), and sockets under/private/tmp/codex-browser-use/and/private/tmp/codex-app-tools/, replaced at each launch; none is collected.
Standalone CLI on Linux¶
Observed on the Ubuntu install of Source and evidence; names, types and sizes only.
~/.codex/packages/standalone/and~/.codex/packages/app-server-daemon/(816,297,770 B together, under the.codex/packagesexclusion):install.lock,auto-update-version(34 B), acurrentsymlink toreleases/0.162.1-aarch64-unknown-linux-musland that release (bin/, acodexsymlink intobin,codex-path/,codex-resources/,codex-package.json). The target triple says the Linux build is statically linked against musl, a C library.~/.local/bin/codex, a symlink to~/.codex/packages/standalone/current/bin/codex(nocodex-code-mode-hostlink, unlike macOS), and a block in~/.bashrcbetween# >>> Codex installer >>>and# <<< Codex installer <<<that prepends~/.local/bintoPATH, as macOS got in~/.zprofile. Neither is collected. No~/.config/codex,~/.local/share/codex,~/.local/state/codexor~/.cache/codex*exists: the CLI uses~/.codexonly, and~/.cache/codex-runtimesbelongs to the desktop app.- The daemon runs from the
app-server-daemonrelease and listens on a socket/tmp/codex-daemon-<uid>/<64 hex>with a.lockbeside it;~/.codex/app-server-control/app-server-control.sockis a symlink to it.app-server-daemon/daemon.pid(423 B) anddaemon-updater.pid(145 B) carryprocessIdentity.{bootId, startTicks}; the parser reads neither.daemon.stderr.logheld onermcp::transport::streamable_http_clienterror line, anddaemon-updater.stderr.logthe JSON lines seen on Windows. - The Linux sandbox is bubblewrap (a tool that runs a process in its own namespaces):
/tmp/codex-bwrap-synthetic-mount-targets-<uid>/lockappeared with the first turn. log/codex-login.log(544 B, mode 0600): fourtracinglines of the browser login, fromcodex_cli::login: starting browser login flowthrough thecodex_login::servercallback line (has_code,has_state,has_error,state_valid) and the token exchange's issuer, token endpoint and redirect URI, tooauth token exchange succeeded. Source-cited above (cli/src/login.rs:85).- Otherwise the source-described files:
auth.json,config.toml(42 B, holding onlytui.screen_reader_detection_done),installation_id,version.json,.sandbox_migration,.sqlite-maintenance.lock,history.jsonl(one line per turn),session_index.jsonl, the SQLite stores with WAL sidecars,models_cache.json,cache/,plugins/cache/openai-curated-remoteandplugins/.remote-plugin-install-staging,skills/.system,tmp/, an emptyshell_snapshots/,thread-writer-locks/.coordination.lockandtui-thread-reference-capabilities/<thread uuid>. A fresh CLI's~/.codexhas none of the desktop app's or Windows' files: nosqlite/codex-dev.db,.codex-global-state.json,.desktop-created,automations,ambient-suggestions,computer-use,visualizations,vendor_imports,plugins/.plugin-appserver,.tmp,cap_sid,node_replor.sandbox*.
Credentials¶
auth.jsonwithOPENAI_API_KEYand OAuth tokens (login/src/auth/storage.rs:47-53,163). Store modeAuthCredentialsStoreMode:file(default),keyring,auto,ephemeral(config/src/types.rs:116-124). Keyring viacodex-keyring-store. As Codex Desktop writes it (observed):OPENAI_API_KEYnull,auth_mode,last_refreshandtokens(access_token,id_token,refresh_token,account_id).ollama launch chatgptcreatesauth.jsonwhen the file does not exist, mode 0600, with{"OPENAI_API_KEY": "ollama-local-codex", "auth_mode": "apikey"}; the key is a fixed local sentinel that Ollama's proxy refuses to forward to OpenAI, not a credential, andollama launch chatgpt --restoredeletes the file only while it still holds that value (Ollamacmd/launch/codex_app.go:561-631,internal/proxy/codex_desktop.go:40-42). The.codex/auth.jsonsecret glob flags it either way; a flaggedauth.jsonwith that value means the integration was set up while no Codex login existed.- On Linux (observed, Ubuntu 26.04.1, Codex CLI 0.162.1 standalone, 2026-10-15) the browser login
wrote the OAuth tokens to
auth.json(4,302 B) at the moment of the token exchange; the GNOME keyring was not used (~/.local/share/keyringsheld only the stocklogin.keyringanduser.keystore).log/codex-login.lognames the login endpoints and the callback's flags, never a token, so it is not a credential (Standalone CLI on Linux). .credentials.json: MCP OAuth token fallback when keyring unavailable (rmcp-client/src/oauth.rs:17,857; config/src/types.rs:138).secrets/gateway OAuth and local secrets, age-encrypted (gateway_oauth.age,local.age,gateway_oauth.lock) (login/src/gateway_auth_storage.rs:20-24, secrets/src/local.rs:166,587-591). At4e7b9e2the lock file is written bylogin/src/oauth_session_storage.rs(21-25), which replacedgateway_auth_storage.rs; the directory and file names are unchanged..sandbox-secrets/Windows sandbox tokens (setup.rs:196). Observed with Codex Desktop:sandbox_users.json(828 B) withofflineandonlineobjects, each{username, password}, andversion: the passwords of the two sandbox accounts. Flagged by.codex/.sandbox-secrets/*..codex-global-state.jsonand its.bak(observed, Codex Desktop only): ChatGPT conversation resume tokens, the account's ids and the user's prompts (Secrets). Flagged by.codex/.codex-global-state.json*(issue 115).shell_snapshots/<thread uuid>.<unix ns>.sh(observed on macOS, Codex Desktop 0.163.0-alpha.10.2 inside ChatGPT 26.1013.40934, 2026-10-18): the user's exported login-shell environment replayed for the sandbox, so any API key the login shell exports is copied into the file; the names exported on the clean lab VM included no key-like name (Thread locks and shell snapshots). Flagged by.codex/shell_snapshots/*(issue 160).- Remote control's server token (observed on macOS, ChatGPT 26.1013.40934, 2026-10-18 and
2026-10-19): the app refreshes it when "Allow connections" is turned back on, and
logs_2.sqliterecords the refresh, never the token.state_5.sqliteremote_control_enrollmentshas no token column and itswebsocket_urlno query string,auth.jsonwas not rewritten after sign-in, and a search of the login keychain for service or account names containingopenai,codexorchatgptwas empty after every step, so where the token is kept was not determined. The enrollment row holds the account id and the Mac's host name but no secret. config.tomlcan embedexperimental_bearer_tokenper provider (model-provider-info/src/lib.rs:149-157);env_keynames an env var instead.- A copy of
auth.jsonoutside the home (Ollama source at6787222):ollama launch chatgptcopies~/.codex/auth.jsonandmodels_cache.jsoninto a scratchCODEX_HOME,ollama-codex-models-*in the system temp directory, to list Codex's native models, and removes it when the command finishes (cmd/launch/codex_app.go:1480-1495). A crash leaves that copy of the Codex credential in the temp directory, which no catalog line covers; see Per-user storage inollama.md, and Credentials incollectors/docs/coverage.md, which names it as a manual step.
At 4e7b9e2 no new credential file was found: the quoted file names ending .age, .json,
.lock, .toml or .env in codex-rs/login, codex-rs/secrets, codex-rs/rmcp-client and
codex-rs/cli differ between the two commits only by a configuration file name, not a credential
store.
Exclusions¶
.codex/packages, .codex/cache, .codex/.tmp, .codex/tmp, .codex/plugins/cache,
.codex/plugins/.plugin-appserver and .codex/plugins/.remote-plugin-install-staging (Codex
Desktop, issue 116),
.codex/skills/.system, .codex/worktrees, .codex/.sandbox,
.codex/.sandbox-bin, .codex/visualizations, .codex/app-server-daemon/packages (if present).
.codex/computer-use/*.app (the computer-use app bundle on macOS, 77 MB,
issue 121).
On the Windows standalone install .codex/packages (794,201,900 B, the standalone and daemon
releases) and .codex/.sandbox-bin (289,784,416 B) fall under these exclusions, so the binaries
need no new one; AppData/Local/Programs/OpenAI/Codex/bin is a junction into .codex/packages
and is not catalogued. On Linux the daemon's socket directory /tmp/codex-daemon-<uid>/ and the
bubblewrap sandbox's /tmp/codex-bwrap-synthetic-mount-targets-<uid>/ are outside the home and
named only by the user id, so no catalog line can hold them.
.codex/db-backups is a copy of the state DBs, small, and may hold rows deleted from the live DB:
collect rather than exclude.
On ChatGPT 26.1013.40934 (macOS, 2026-10-19) the excluded trees under .codex measured cache
39.6 MB (of which remote_plugin_catalog/ 38.7 MB), plugins/cache 67 MB,
plugins/.plugin-appserver 341 MB, .tmp 123 MB and computer-use/Codex Computer Use.app 78 MB,
beside the small plugins/.remote-plugin-install-staging, skills/.system, tmp and
visualizations; nothing new and large sat outside them. Library/Application
Support/com.openai.codex is updater state only, collected as chatgpt-desktop
(issue 121).
Project-local files¶
AGENTS.md, AGENTS.override.md
(core/src/agents_md.rs:43-45),
.codex/config.toml, .codex/agents/, .codex/hooks.json, .codex/hooks/, .codex/rules/,
.codex/skills/
(external-agent-migration/src/scope.rs:59-62,
detect/mod.rs:86,167,270;
exec-server/src/discoverV2/capability_locations.rs:72),
.agents/skills
(external-agent-migration/src/service.rs:467),
generated_images/ in cwd
(ext/image-generation/src/tool.rs:332).
No per-project database.
Where the project path is recorded¶
- First line of every rollout:
SessionMetawithcwd: PathBuf,timestamp,id,originator,cli_version,source,runtime_workspace_roots(protocol/src/protocol.rs:3123-3153). Compressed rollouts need zstd before grep. Project discovery reads it insessions/and, from collector 1.18.0, inarchived_sessions/, where an archived thread's rollout moves (What survives a delete). It drops acwdthat is<home>/Documents/Codexor lies under it, the desktop app's projectless output directories, which thecodex-cli|Documents/Codexcatalog line collects instead, and, from 1.18.0, acwdunder<home>/.codex/worktrees, the thread worktrees (Scratch directories; issue 149, issue 222). state_5.sqlitethreadscwd, which the analyzer reads (state_5.sqlitethreads).session_index.jsonlholds titles only, no path (session_index.jsonl).config.toml[projects."<path>"]tables, each withtrust_leveltrustedoruntrusted(Standing grants and approval records); on macOS (ChatGPT 26.1013.40934, 2026-10-18) adding a project folder in the app wrote one.- Codex Desktop (observed):
state_5.sqliteproject_rootspath;sqlite/codex-dev.dblocal_thread_catalogcwd; and in.codex-global-state.json,local-projectsrootPaths,thread-workspace-root-hintsandthread-projectless-output-directories. On the Windows install both rollouts'cwdvalues named the same directories, so the rollouts are enough for discovery.
Parsing¶
Transcript stores¶
Paths relative to the home; $CODEX_HOME replaces .codex when set.
.codex/sessions/YYYY/MM/DD/rollout-<YYYY-MM-DDThh-mm-ss>-<thread id>.jsonl, one per thread, the directory date and file-name time in local time (rollout/src/recorder.rs:1722-1734, rollout_file_name.rs:62-73), while every timestamp inside is UTC: on the macOS install of 2026-10-18, at UTC-4, a thread started at 18:40:09Z isrollout-2026-10-18T14-40-09-<uuid>.jsonland one started at 23:18:38Z issessions/2026/10/18/rollout-2026-10-18T19-18-38-<uuid>.jsonl. The parser takes every time from the records, never from the file name.thread/revertwrites a new filerollout-<time>-<thread id>_<rollout id>.jsonlthat keeps the thread id and points back at its prefix (recorder.rs:95-104, rollout_file_name.rs:40-47). Subagent threads get their own rollout in the same tree. Parser reads..codex/archived_sessions/, same layout (rollout/src/lib.rs:86-87). Parser reads.*.jsonl.zst: zstd level 3, written by a background worker for rollouts idle seven days, in both trees (compression.rs:29, 362-363, 500-501). The worker runs only with thelocal_thread_store_compressionfeature, which is under development and off by default (features/src/lib.rs:1212-1217, core/src/thread_manager.rs:461-503). Parser reads (needszstandard)..codex/history.jsonl: one line per submitted prompt, all threads (Other stores). Parser reads..codex/session_index.jsonl: thread renames (Other stores). Not read..codex/automations/<slug>/automation.toml: a scheduled automation of the ChatGPT desktop app (Other stores). Parser reads..codex/state_5.sqlite,.codex/thread_history_1.sqlite: thread metadata and a projection of paginated rollouts (Other stores). Parser reads, as secondary sources (issue 122).- Copies held by other agents. Three agents copy these transcripts into stores of their own, where the copy outlives the original file (from those agents' source; none seen on a real install):
- Hermes Agent's
hermes_cli/foreign_sessions.pyreads Codex's~/.codex/sessions/**/rollout-*.jsonland appends their user and assistant turns to itsstate.dbas a new session whoseorigin_json.imported_fromnames the tool, the source path and the foreign session id, stamped with the import time (hermes_cli/foreign_sessions.py:254-260; Transcript stores inhermes.md). - Cline's SDK session import makes an ordinary Cline session with a new id and
metadata.importedFrom{tool, sourceSessionId, sourcePath, importedAt, sourceProvider, sourceModel}; at the reviewed commit only Cline's example desktop app calls it (sdk/packages/core/src/services/session-import/service.ts:349-366; Transcript stores incline.md). - Pydantic AI's CLAI 2.0 (its command-line chat client)
/resumecopies them into itssessions.db(codex_sessions.py:35-37; Other agents' sessions inpydantic-clai.md). - Indexed by Zed. Zed's agent panel can run this agent as an external agent over ACP (the Agent
Client Protocol, the JSON-RPC protocol Zed uses to drive another agent); its threads sidebar then
lists the session in
sidebar_threadswithagent_idcodex-acpand the ACP session id insession_id, while the transcript stays in this agent's own store (agent_servers/src/custom.rs:17-19; Transcript stores inzed.md). Whether that id equals the session id this agent records was not determined.
Observed rollouts¶
The rollouts of the real installs of Source and evidence, by line type; none
had a bad line, and every one was history_mode paginated.
| Install | Thread | Size, lines | Line types |
|---|---|---|---|
| macOS, Codex Desktop 0.163.0-alpha.10.2, 2026-10-18 | plain question, projectless | 190,766 B, 21 | session_meta 1, event_msg 6 (task_started, thread_settings_applied, item_completed 2, token_count, task_complete), response_item 11 (additional_tools 1, message with role developer 6, user 3, assistant 1), world_state 1, turn_context 1, token_usage_record 1 |
| same | coding task in ~/Demo, first turn |
205,378 B, 39 | session_meta 1, turn_context 1, world_state 1, response_item 18 (developer 6, user 3, assistant 2, additional_tools 1, custom_tool_call 3, custom_tool_call_output 3), event_msg 14 (item_completed 7: CommandExecution 3, one failed with exit_code 1, FileChange 1, AgentMessage 2 with phase commentary, UserMessage 1; token_count 4, task_started, thread_settings_applied, task_complete), token_usage_record 4 |
| same | guardian review thread | 105,545 B, 17 | session_meta, task_started, 5 response_item, world_state, turn_context, thread_settings_applied, item_completed Reasoning and AgentMessage, token_usage_record, token_count, task_complete |
| same | scheduled task's thread | 42 lines after Run now, 62 after the scheduled run | not tabulated |
| Ubuntu, CLI 0.162.1, 2026-10-15 | two turns in ~/demo, sessions/2026/10/15/rollout-2026-10-15T13-16-43-<uuid>.jsonl |
440,924 B, 108 | session_meta 1, turn_context 2 (one per turn), world_state 1, response_item 41, event_msg 49, token_usage_record 14 |
| macOS, Codex Desktop 0.162.0-alpha.17.2 and CLI 0.162.1, 2026-10-15 | four threads under sessions/2026/10/15/ |
348 KB in all | not tabulated |
| Windows, CLI 0.162.1, 2026-10-15 | one prompt in ~\demo-cli, rollout-2026-10-15T11-41-45-<uuid>.jsonl |
106,200 B, 47 | session_meta 1, turn_context 1, world_state 1, response_item 20, event_msg 18, token_usage_record 6 |
| Windows, Codex Desktop 0.162.0-alpha.17.2, 2026-10-15 | projectless thread | 37 lines | session_meta 1, turn_context 1, world_state 1, response_item 17, event_msg 13, token_usage_record 4 |
| same | project task on the Desktop | 32 lines | session_meta 1, turn_context 1, world_state 1, response_item 15, event_msg 11, token_usage_record 3 |
| same | automation's target thread | 83,579 B | one user turn, one assistant message, no tool call (Automations) |
| development host, Codex 0.160.0 | one thread | 20 lines | tabulated by type only (Record schema) |
The models seen were gpt-6.1-sol (the 2026-10-18 macOS threads before the switch, the Ubuntu and
Windows CLI threads and the three 2026-10-15 macOS threads other than the automation's),
gpt-5.6-terra (the 2026-10-15 automation threads on Windows and macOS, chosen by the app, and the
2026-10-18 Demo thread after the switch), gpt-6-luna (the two first Windows desktop threads)
and codex-auto-review (the guardian review thread).
What survives a delete¶
A thread/delete request (the TUI and the desktop app both go through the app server) refuses a
thread whose rollout a fork still references, then removes, for the thread and its spawned
subagents: the rollout under sessions/ and under archived_sessions/, both the .jsonl and the
.jsonl.zst form, and the thread's session_index.jsonl lines
(app-server/src/request_processors/thread_delete.rs:31-63,
thread-store/src/local/delete_thread.rs:95-136,
204-281);
its thread_history_1.sqlite rows
(thread-store/src/local/thread_history.rs:242);
its agent message boards
(core/src/thread_manager.rs:484-496);
and the logs_2.sqlite rows whose thread_id column is the thread, its queued items in
queue_1.sqlite, its memories stage1_outputs row, its goal in goals_1.sqlite, and its
state_5.sqlite rows in thread_dynamic_tools, thread_spawn_edges, thread_read_receipts and
threads
(state/src/runtime/threads.rs:1166-1216)
(source at 4e7b9e2). An archive (thread/archive) instead moves each rollout into
archived_sessions/ under its own file name, without the date directories
(thread-store/src/local/archive_thread.rs:67-130).
Neither delete handler touches history.jsonl (grepped
delete_thread.rs
and
thread_delete.rs
for history.jsonl and message_history), which keeps one line per prompt typed into the TUI with
the thread id and its time in seconds (history.jsonl) and is then the one place
such a prompt survives. A delete also leaves the memories files until the next consolidation, the
db-backups/ copies of a database Codex found corrupt
(Other past-use stores), and log rows that name the thread only in their
text.
The desktop app's own delete (macOS, build 26.1013.40934, 2026-10-19; the dialog mentioned no
remote copy) was a hard delete: no archived_sessions/ directory was created. It removed the
rollout, its session_index.jsonl line, its state_5.sqlite threads and
thread_read_receipts rows, its thread_history_1.sqlite rows, its sqlite/codex-dev.db
local_thread_catalog row and the global state's thread-client-id-v1:local%3A<thread id> key;
logs_2.sqlite has the fs/remove and thread/delete requests of that second. It left the
global-state keys that name the thread (its prompt-history entry with the prompts,
thread-descriptions-v1, thread-user-activity-times-v1, heartbeat-thread-permissions-by-id,
client-thread-bindings-v1, and the top-level projectless-thread-ids,
thread-projectless-output-directories and thread-workspace-root-hints) and added
codex-writing-block-deleted-thread-v1:<uuid> set to true, the deletion marker; it also left the
empty projectless output directory ~/Documents/Codex/2026-10-18/w and at least one
logs_2.sqlite row carrying the thread id. Source deletes only the log rows whose thread_id
column matches, so which rows survived (rows with the id only in their message text, rows written
after the delete, or a build that did not yet delete log rows) was not determined.
Hermes Agent, Cline's SDK and Pydantic AI's CLAI hold copies that outlive the original rollout
(Transcript stores). The analyzer reads the archived rollouts as primary,
history.jsonl, the global state and the db-backups/ copies of state_5.sqlite as secondary
sources (the marker as a session deleted: row), and the memory rollout summaries as a source of
their own, since they repeat no event (see
Secondary sources).
Record schema¶
Each line is a RolloutLine: timestamp, optional ordinal, and the item
flattened beside them
(history/src/lib.rs:356-367,
recorder.rs:2062-2091).
The item serialises as {"type": <snake_case variant>, "payload": {...}};
response_item lines may also carry a sibling metadata object
(history/src/rollout_payload.rs:30-72).
Codex's own reader requires timestamp and treats ordinal as optional
(rollout/src/lib.rs:52-74).
ordinal is written only in paginated rollouts, counting from 0 or from the
history_base the file continues
(rollout/src/ordinal.rs:22-44).
Line types (history/src/lib.rs:209-227); which ones are written is decided in rollout/src/policy.rs:22-50:
type |
payload |
Seen locally |
|---|---|---|
session_meta |
thread header, below | yes |
turn_context |
per-turn settings, below | yes |
response_item |
a Responses API item (Message content and tool calls) | yes |
event_msg |
a UI event, tagged by its own type (below) |
yes |
token_usage_record |
thread_id, turn_id, session_id, root_turn_id, response_id, usage, turn_token_usage, thread_token_usage (protocol.rs:2263-2273) |
yes |
world_state |
full, state (model-visible environment snapshot or patch, protocol.rs:3272-3278) |
yes |
compacted |
message (the compaction summary), replacement_history, window ids (history/src/lib.rs:285-306, rollout_payload.rs:238-266) |
no |
inter_agent_communication |
author, recipient, other_recipients, content (plain text), encrypted_content, trigger_turn (protocol.rs:806-822) |
no |
inter_agent_communication_metadata |
trigger_turn |
no |
retained_context |
verified answers and delivered messages for the guardian reviewer (history/src/retained_context.rs:145-162) | no |
security_risk_score |
scores, call_id, action, sampled_at (security_risk.rs:12-25) |
no |
realtime_item |
id, realtime_session_id, tagged content incl. transcript_segment (role, text); paginated only (realtime.rs:6-31) |
no |
session_meta¶
session_meta (line 1;
protocol.rs:3117-3198):
id (this thread), session_id (the root thread's id; filled from id when absent in old files,
3243-3269),
forked_from_id, parent_thread_id, timestamp, cwd, runtime_workspace_roots, originator
(codex-tui locally), cli_version, source (cli, vscode, exec, mcp, a custom string, or
{"subagent": ...};
2824-2837,
2905-2923),
thread_source (user, subagent, guardian_review, memory_consolidation or a feature name;
2843-2849),
agent_nickname, agent_role, agent_path, model_provider, base_instructions,
dynamic_tools, memory_mode, history_mode (legacy or paginated), history_base,
context_window, and the account fields creator_user_id, creator_account_id. Beside them, git
(commit_hash, branch, repository_url;
3436-3452)
is written only when cwd is inside a git repository
(recorder.rs:1978-1990).
repository_url is a SanitizedGitUrl, a remote URL with its credentials removed before it is
stored: every password, and every username other than the ssh git user, is dropped
(sanitized_git_url.rs:11,
73-98).
The type arrived in commit
1bc02ae
of 2026-08-25, so a rollout an earlier build wrote, and one a fork without that commit wrote
(Open Interpreter is such a fork), can hold the URL with its credentials; the parser writes the
stored string as it is.
Observed on Codex Desktop (Windows, 0.162.0-alpha.17.2, 2026-10-15; macOS, 0.163.0-alpha.10.2,
2026-10-18) and in the Windows standalone CLI's rollout: id, session_id, timestamp, cwd,
runtime_workspace_roots (1 entry), originator, cli_version, source vscode,
thread_source user, model_provider openai, base_instructions (text, of 21,769
characters on macOS, and provenance with type model and model), history_mode
paginated, context_window (window_id), creator_user_id (29 characters) and
creator_account_id (36 characters, a UUID). Every top-level key is in the source list above;
base_instructions.provenance is observed, not checked against source at
3e23877. No
real install had a git block, as no thread ran in a repository. A guardian review thread adds
parent_thread_id (absent on user threads) and multi_agent_version disabled
(Guardian review threads).
turn_context¶
turn_context (once per user turn and after mid-turn compaction;
protocol.rs:3296-3358):
turn_id, root_turn_id, cwd, workspace_roots, current_date, timezone, approval_policy,
sandbox_policy, permission_profile, network, model, collaboration_mode (with its own
settings.model), effort, summary, personality and more. The four policy fields:
approval_policy is a kebab-case string, untrusted, on-request (the default, also read from the
old on-failure) or never, or an object {"granular": {...}} for per-flow controls
(protocol.rs:986-1009);
sandbox_policy is an object tagged by type: danger-full-access, read-only,
external-sandbox or workspace-write, the last three with fields of their own
(protocol.rs:1072-1120);
permission_profile, optional, is an object tagged by type: managed (file_system, network),
disabled or external (network)
(models.rs:421-435);
network, optional, is {allowed_domains, denied_domains}, two lists of strings
(protocol.rs:3291-3294).
Observed on Codex Desktop (Windows, 2026-10-15; macOS, 2026-10-18): turn_id, root_turn_id,
cwd, workspace_roots (2), current_date, timezone, approval_policy, sandbox_policy
(type workspace-write, writable_roots, 2 on macOS, network_access,
exclude_tmpdir_env_var, exclude_slash_tmp), permission_profile (type managed,
file_system with type restricted and 13 entries, network restricted), model
(gpt-6-luna on Windows, gpt-6.1-sol on macOS), collaboration_mode (mode default,
settings with model, reasoning_effort, medium on Windows, and developer_instructions, of
1,288 characters on macOS), effort (medium on Windows, low on macOS), and on Windows summary
detailed and personality friendly. approval_policy was the granular object
(mcp_elicitations, request_permissions, rules, sandbox_approval, skill_approval; on
Windows request_permissions and mcp_elicitations true, the rest false) in the first thread of
each install and the string on-request in later ones. Observed and not among the fields listed
above from 3e23877:
disabled_plugin_ids (empty), approvals_reviewer user, active_permission_profile (id
:workspace), file_system_sandbox_policy (kind restricted, 13 entries on Windows, 18 on
macOS), comp_hash, multi_agent_version v2, realtime_active and cyber_access_program
standard; the drift to 4e7b9e2 in Format versions lists those the struct has
there. Full access and the guardian thread give other values
(event_msg, Guardian review threads).
world_state¶
world_state.payload is full and state, the model-visible environment snapshot or a patch to it
(protocol.rs:3272-3278).
The keys observed on Codex Desktop (macOS, 0.163.0-alpha.10.2, 2026-10-18): tools (the
mcp__codex_apps__* tools github, pets, hotline, sites, search_service,
safety_settings, plugin_management and codex_document_control, plus mcp__code_review and
mcp__node_repl), top_level_tools (clock.*, collaboration.* with spawn_agent and
send_message, functions.exec, functions.request_user_input, functions.wait,
mcp__cua_repl.*), permissions (approved_command_prefixes, instructions), environments
(current_date, timezone, filesystem), host_skills, multi_agent_mode (mode
explicitRequestOnly) and git_attribution. The parser skips the line
(Parser plan).
event_msg¶
event_msg (tagged enum,
protocol.rs:1352-1358).
Persisted in every mode
(policy.rs:127-139):
task_started (turn_id, root_turn_id, trace_id, started_at epoch s, model_context_window,
collaboration_mode_kind), task_complete (turn_id, last_agent_message, error, started_at,
completed_at, duration_ms, time_to_first_token_ms)
(2152-2197;
the wire names are kept from v1,
1407-1418),
turn_aborted (turn_id, reason, error, times;
4243-4263),
thread_rolled_back (num_turns;
3692-3696),
token_count, thread_goal_updated, thread_settings_applied, and item_completed (below).
Legacy-mode rollouts also keep user_message (message, images), agent_message (message,
phase), agent_reasoning and agent_reasoning_raw_content (text), entered_review_mode,
exited_review_mode, patch_apply_end, context_compacted, mcp_tool_call_end, web_search_end,
image_generation_end and sub_agent_activity
(policy.rs:141-158;
protocol.rs:2518-2544,
2637-2645).
These repeat what the response_item lines carry.
item_completed (thread_id, turn_id, item, started_at_ms,
completed_at_ms;
protocol.rs:1947-1960)
carries a TurnItem tagged by PascalCase type: UserMessage,
AgentMessage, Reasoning, CommandExecution (command, cwd,
aggregated_output, exit_code, duration), McpToolCall (server,
tool, arguments, result), FileChange (changes, stdout,
stderr), WebSearch, Extension (kind web.search, clock.sleep or
image_gen.generation), Plan, CollabAgentToolCall (prompt, model,
receiver thread ids) and others
(items.rs:42-78,
243-281,
337-422,
443-486;
ext/items/src/lib.rs:32-45).
Paginated rollouts persist every completed item, with command output and
MCP results cut to 64 KiB; legacy rollouts keep only FunctionCallOutput,
Plan, clock.sleep and completed SubAgentActivity items
(policy.rs:16-19,
227-283).
Locally the paginated rollout held UserMessage, Extension
(web.search, with query, action and results) and AgentMessage
items.
Observed on Codex Desktop (macOS, 0.163.0-alpha.10.2, 2026-10-18):
item_completeditems:CommandExecutionwithid,process_id,cwd,parsed_cmd,source,status,aggregated_output,exit_codeandduration(secs,nanos);FileChangewithchangeskeyed by the absolute paths,statuscompleted,stdoutandstderr;AgentMessagewithphase;UserMessagewithclient_id,contentandid(the runs of a scheduled task have noclient_id);Extensionwithkindweb.search(action,id,kind,query,results,type);Reasoning(id,raw_content,summary_text).task_started.turn_attribution:turn_id,turn_trigger,parent_turn_id,initiating_agent_pathandroot_turn_id(in source from4e7b9e2, Format versions). Theturn_triggervalues of this build:composer(a prompt typed in the app),remote_iosandremote_android(a prompt from a paired phone, whoseturn_contexthas the shape of a composer turn; the trigger is the only mark in the rollout that a turn came from a phone),guardian_review,scheduled_task_setup,automation_heartbeat_run_nowandautomation_heartbeat_scheduled(Automations).task_started.model_context_windowwent from 475000 to 258400 at the model switch.token_count.rate_limits:limit_id(codex),plan_type(plus),primary,secondary,credits,limit_name,individual_limit,spend_control_reachedandrate_limit_reached_type, so every rollout names the account's plan.thread_settings_applied, the thread's settings, appended the moment the user changes one, before any prompt, and again before the next turn:thread_settingswith 14 keys,active_permission_profile,approval_policy,approvals_reviewer,collaboration_mode,cwd,disabled_plugin_ids,model,model_provider_id,permission_profile,personality,reasoning_effort,reasoning_summary,runtime_workspace_rootsandservice_tier, and nosandbox_policy. Opening a thread after a restart appends one with 11 keys (the 14 lesspersonality,reasoning_summaryandservice_tier; Joins and timestamps). The model switch setthread_settings.modeltogpt-5.6-terra(the three earlier events saidgpt-6.1-sol), and the nextturn_contextfollowed. Approve for me setapprovals_revieweruser->auto_review, withapproval_policystillon-request,permission_profilemanagedandactive_permission_profileid:workspace; a third, identical event preceded the turn. Full access setapproval_policynever,approvals_revieweruser,active_permission_profileid:danger-full-accessandpermission_profile{"type": "disabled"}, and the turn'sturn_contextaddedsandbox_policydanger-full-accessandfile_system_sandbox_policynull; setting it back appended an event with the earlier values (on-request,auto_review,:workspace,managed). The app does not turn Full access off by itself: it stays a thread setting until changed.state_5.sqlitethreads(model,approval_mode) and the global state keep only the latest values, so a model switch and a full-access turn are recorded only in the rollout andlogs_2.sqlite.- No approval event. A write inside the workspace ran in the sandbox with no request, and a declined
or approved call left only its
CommandExecution(failedorcompleted) and no change toturn_context; the decisions are inlogs_2.sqlite. The declined call's output was the textScript error: exec_command failed: CreateProcess { message: "Rejected(\"rejected by user\")" }, after the samecurlhad failed in the sandbox withexit_code6, the network sandbox (network_accessfalse).
Everything else on the EventMsg enum (deltas, begin events, approvals,
exec_command_end, errors, warnings) is transient and never written
(policy.rs:160-223).
Message content and tool calls¶
response_item.payload is a ResponseItem tagged by snake_case type
(protocol/src/models.rs:1010-1256);
all variants but compaction_trigger and unknown ones are written
(policy.rs:78-100).
Most variants carry an optional id and
internal_chat_message_metadata_passthrough (turn_id, create_time in
fractional epoch seconds, content_item_kinds;
models.rs:954-994).
message:role(user,assistant,developer; the harness's own instructions go in asdeveloper),content[]ofinput_text,input_image(image_urlorfile_id),input_audiooroutput_textblocks, each withtextwhere textual (876-896), andphase(commentaryorfinal_answer; 938-952, 1021-1036). Arole: "user"message is not always typed by the user: the harness injectsAGENTS.mdinstructions,<environment_context>, skill text, subagent notifications, turn-aborted notices and user shell commands as user-role messages (core/src/context/contextual_user_message.rs:22-37). Current versions label each content block incontent_item_kinds; typed input isuser.text, the environment blockenvironments.environment_context(contextual_user_message.rs:51-82, core/src/context/world_state/environment.rs:200-203). Locally the first turn had threedevelopermessages and twousermessages, of which one wasenvironments.environment_contextand oneuser.text.reasoning:summary[]ofsummary_text(text),content[]ofreasoning_textortext,encrypted_content(1048-1060, 1982-1993).contentis written only when it holdsreasoning_textparts or is null (1624-1631).function_call:name,namespace(MCP and plugin tools),arguments(a JSON string),call_id(1074-1093).custom_tool_call:status,call_id,name,namespace,input(free text) (1134-1151). Code mode'sexectool (code-mode-protocol/src/lib.rs:52) and the freeformapply_patcharrive this way; the one local call wasexec.local_shell_call:call_id,status,action(type: "exec",commandarray,timeout_ms,working_directory,env,user) (1061-1073, 1937-1950).function_call_output,custom_tool_call_output:call_id,name,output, which is a string or an array ofinput_text,input_image,input_audio,encrypted_contentitems (1114-1133, 1155-1169, 2096-2118, 2250-2263). Locally an array of twoinput_textitems.web_search_call:status,action(searchwithquery/queries,open_pagewithurl,find_in_pagewithurl,pattern) (1191-1204, 1952-1980). No output item.tool_search_call(call_id,execution,argumentsobject) andtool_search_output(call_id,status,tools[]) (1094-1108, 1170-1182).agent_message:author,recipient,content[]ofinput_textorencrypted_content(906-911, 1037-1047).image_generation_call:status,revised_prompt,result(image data) (1214-1226).additional_tools(tool definitions),compactionandcontext_compaction(encrypted_content),configuration_update: no readable conversation content (1013-1020, 1227-1253).
The metadata sibling on a response_item line is harness bookkeeping:
client_authored, retained_source, user_input_order,
inherited_user_message (copied from a parent thread), mcp_attribution
and others
(history/src/lib.rs:55-135).
Observed on Codex Desktop (macOS, 2026-10-18): messages carry
internal_chat_message_metadata_passthrough; the line metadata keys were client_authored,
mcp_attribution, retained_source and user_input_order; the assistant message has phase and
status; the coding task's custom_tool_call lines had call_id, id, input, name and
status. Tool names seen on the real installs: exec, shell, web_search and apply_patch;
node_repl.js, a tool of the node_repl MCP server the desktop app's config.toml configures
(Windows CLI); codex_app.load_workspace_dependencies (macOS desktop, 2026-10-15); and
request_user_input_async (Ubuntu CLI), the CLI asking the user a question mid-turn.
Guardian review threads¶
In the Approve for me mode (Codex Desktop, macOS, 0.163.0-alpha.10.2, 2026-10-18) an approval
request opens no dialog: the app starts a reviewer thread with its own rollout
(Observed rollouts). Codex calls the reviewer the guardian (thread_source
guardian_review) and the mode auto-review (approvals_reviewer auto_review); the app's global
state calls the mode guardian-approvals. The reviewer's session_meta has source
{"subagent": {"other": "guardian"}}, thread_source guardian_review, parent_thread_id the
governed thread, the same cwd and two runtime_workspace_roots; its turn_context has model
codex-auto-review, approval_policy never, approvals_reviewer auto_review and
sandbox_policy read-only; turn_attribution.turn_trigger is guardian_review.
task_complete.last_agent_message, and the AgentMessage item, is a JSON string with risk_level
(low), user_authorization (high), outcome (allow) and rationale (free text): the
automatic decision for the call. The parent rollout then shows the governed FileChange
completing (21:27:27Z on the install) with no approval event of its own; logs_2.sqlite joins the
two (logs_2.sqlite). The reviewer thread has a state_5.sqlite threads row
(thread_source guardian_review, model codex-auto-review, approval_mode never,
has_user_event 0), items in thread_history_1.sqlite and a session_index.jsonl line named
Guardian review, but no thread_spawn_edges row. The app also prewarms reviewer sessions that
write no rollout (Thread locks and shell snapshots).
Joins and timestamps¶
- Timestamps. Line
timestampandsession_meta.payload.timestamp: UTCYYYY-MM-DDThh:mm:ss.mmmZ, the line one taken at write time (recorder.rs:927-933, 2077-2082); local values all had three fraction digits andZ.task_*started_at/completed_at: epoch seconds;item_completed*_at_ms: epoch ms;create_time: fractional epoch seconds;history.jsonlts: epoch seconds. - Ids.
session_meta.payload.idis the thread id and the file-name UUID;session_idis the root thread's id, so a subagent's rollout hasid≠session_id. Reverted rollouts end<thread id>_<rollout id>. - Surface.
session_meta.payload.originatoris the key that tells the surfaces apart:codex-tuifor the CLI andCodex Desktopfor the ChatGPT desktop app.sourcewasvscodefor both on the Windows install observed on 2026-10-15 (desktop 0.162.0-alpha.17.2, standalone CLI 0.162.1), so it does not separate them. The two surfaces write one.codextree, its sessions and its login, and their rollouts have the same envelope andsession_metakeys, differing inoriginatorandcli_version; the parser reads both alike. - Account and user.
session_meta.payload.creator_account_id(a UUID) andcreator_user_id(the ChatGPT user id, 29 characters) name who created the thread; Codex Desktop fills them (observed on Windows, 2026-10-15). The same pair is in thestate_5.sqlitethreadscolumns of the same names, and the user id, with the ChatGPT workspace id, in thecodex-dev.dblocal_thread_catalog_hostshost idchatgpt:<workspace uuid>:<user id>(Other stores). - Project.
session_meta.payload.cwd, updated per turn byturn_context.payload.cwd;workspace_rootslists extra roots. - Branch.
session_meta.payload.git.branch, set once at thread creation, absent outside a git repository. - Model.
turn_context.payload.model;model_provideronsession_meta. No per-message model. Codex Desktop also appendsevent_msgthread_settings_applied(model,reasoning_effortand the permission fields) the moment the user changes a setting, before the next turn (event_msg);state_5.sqlitethreads.modelkeeps only the latest. - Resume. Resuming a thread appends to its rollout: no new file, no
second
session_metaand no resume event (Codex Desktop, macOS, 2026-10-18). The one mark is the 11-keythread_settings_appliedthe app appends when the thread is opened (event_msg);logs_2.sqlitehas thethread/resumerequests (21:46:06Z and 21:46:11Z on the install) withapp_server.client_nameCodex Desktopandapp_server.client_version26.1013.40934. The model switch and the permission mode survived the restart. - Subagents. Each spawned agent has its own rollout whose
session_metahasparent_thread_id,agent_nickname,agent_role,agent_path,thread_source: "subagent"andsource: {"subagent": {"thread_spawn": {parent_thread_id, depth, ...}}}; messages between agents areinter_agent_communicationlines. Codex Desktop's guardian reviewer has nothread_spawnand nothread_spawn_edgesrow (Guardian review threads). - Forks.
forked_from_idon the child. A copied fork re-appends the parent's records into the child file at fork time, so they carry the fork's line timestamps; a referenced (paginated) fork keeps them in the parent file behindhistory_base(thread_id,end_ordinal_exclusive,end_byte_offset) (core/src/session/mod.rs:1672-1695, protocol.rs:3101-3115). Codex's own metadata sync only trusts asession_metawhoseidis the file's thread, which implies copied files can hold an ancestor'ssession_metatoo (thread-store/src/thread_metadata_sync.rs:246-256). - Joins. Tool call to result:
call_id, equal on the call and its*_output. Turn:turn_idonturn_context,task_started,task_complete,item_completedand in each item's metadata passthrough. Anitem_completeditem that a code-modeexeccall produced (the localweb.search) has its own id, not the call'scall_id, so nothing joins it to the call except its position between the call and its output (local).
Other stores¶
history.jsonl¶
Each line is {session_id, ts, text}, ts epoch seconds, session_id the thread id, one line
per prompt typed into the TUI
(message-history/src/lib.rs:61-66,
127-138).
Written unless history.persistence = "none"
(config/src/types.rs:211-219);
trimmed oldest-first when history.max_bytes is set
(message-history/src/lib.rs:191-195).
It can outlive the rollout it names (deleted or ephemeral threads), so it is the one place such a
prompt survives. Local: one line, those three keys, 10-digit ts. The standalone CLI wrote one
line per turn with those keys on every real install (Windows and macOS, 2026-10-15; Ubuntu,
2026-10-15, two lines and 370 bytes); Codex Desktop writes none and keeps its prompts in the global
state's prompt-history (.codex-global-state.json).
session_index.jsonl¶
One {id, thread_name, updated_at} line is appended per rename, newest wins, updated_at RFC 3339
UTC with nanoseconds
(rollout/src/session_index.rs:24-48).
Titles only; local shape matches. Codex Desktop also lists the guardian review thread, as
Guardian review, and a scheduled task's thread, under the task's name (macOS, build 26.1013.40934,
2026-10-18; Source and evidence).
state_5.sqlite threads¶
state_5.sqlite threads: id, rollout_path, created_at, cwd, title, git_sha,
git_branch, git_origin_url and later first_user_message, preview
(state/migrations/0001_threads.sql:1-19,
0007_threads_first_user_message.sql:1,
0032_threads_preview.sql:1);
thread_spawn_edges links parent and child threads
(0021_thread_spawn_edges.sql:1-5).
A row whose rollout is gone still holds the first prompt. The file name is fixed beside the other
databases
(state/src/sqlite.rs:34-39).
The columns the parser reads (issue 122),
each only when the table has it:
id, the thread id: the rollout'ssession_meta.id, so the rows join the rollout's onsession_id.created_atin epoch seconds andcreated_at_msin epoch ms (0025_thread_timestamps_millis.sql:1-2), both the thread's creation time (state/src/runtime/threads.rs:666-670).git_origin_url, the repository's remote URL when the thread'scwdwas in a git repository, read for thegit remote:row (issue 253).cwd,title,git_branch,rollout_path,archived,model(the last turn's, set from eachturn_context; state/src/extract.rs:122-130),name(a rename; 0041_threads_name.sql:1),sourceandthread_source(0030_threads_thread_source.sql:1).first_user_message: the firstUserMessageevent's text, trimmed and with the## My request for Codex:prefix removed, or[Image]or[Audio]for a prompt with no text (state/src/extract.rs:184-196, protocol/src/protocol.rs:145-150, 2614-2635). No column records when the prompt was typed:created_atis the thread's creation, which the TUI does before the first prompt.
Not read: sandbox_policy and approval_mode (the latest
turn_context policy, which the rollout's permission mode: rows
already carry), preview (the first prompt again, or a thread goal's
objective), the token and recency bookkeeping. What it survives: a
rollout deleted, moved or truncated outside Codex, or excluded by the
collector's size limit. Codex's own thread delete removes the rollout
and the database rows together
(thread-store/src/local/delete_thread.rs:66-92),
so it survives no delete made in Codex. Whether a rollback or a revert
rewrites first_user_message was not determined.
Observed on Codex Desktop (Windows, 2026-10-15, 2 rows): the columns id, rollout_path,
created_at, updated_at, source, model_provider, cwd, title, sandbox_policy,
approval_mode, tokens_used, has_user_event, archived, archived_at, git_sha,
git_branch, git_origin_url, cli_version, first_user_message, agent_nickname,
agent_role, memory_mode, model, reasoning_effort, agent_path, created_at_ms,
updated_at_ms, thread_source, preview, recency_at, recency_at_ms, history_mode,
name, is_pinned, thread_section_id, section_position, section_entered_at_ms,
project_id, originator, daybreak_enabled, creator_user_id and creator_account_id.
approval_mode held the approval policy: the first thread's a JSON object {"granular":
{"sandbox_approval": false, "rules": false, "skill_approval": false, "request_permissions": true,
"mcp_elicitations": true}}, the same object as its turn_context.approval_policy, the second's
the string on-request; sandbox_policy held JSON of 2,389 and 1,725 characters. Beside the tables
the source describes, the database held projects (1 row), project_roots (1; project_id,
position, path), project_idempotency_keys, thread_sections (1),
remote_control_enrollments (0) and external_agent_config_imports (0); on macOS (2026-10-18)
also guardian_review_feedback, thread_spawn_edges and thread_read_receipts. Adding a project
folder wrote a projects and a project_roots row, and a model switch overwrote threads.model
(macOS, 2026-10-18).
thread_history_1.sqlite¶
thread_history_1.sqlite: thread_turns, thread_items (item_json,
the app-server's camelCase ThreadItem), thread_realtime_items, keyed
by rollout_ordinal
(state/thread_history_migrations/0001_thread_history.sql:1-38).
A projection of paginated rollouts; local item types were userMessage,
webSearch, agentMessage, the same three items as the rollout's
item_completed lines. Nothing found that is not in the rollout.
Codex Desktop on Windows (observed 2026-10-15,
Source and evidence) added reasoning, commandExecution and
fileChange items, also all in its rollouts: thread_items held 13 rows
(agentMessage 4, commandExecution 2, fileChange 1, reasoning 2,
userMessage 2, webSearch 2) and thread_turns 2 rows, both completed.
On macOS (2026-10-18) the file was created with the first thread (thread_items
2, thread_turns 1), a call the user declined is a commandExecution
failed with exit 1, and the item_json keys were userMessage
(clientId, content, id, text, text_elements, type),
agentMessage (delivery, id, memoryCitation, phase, questions,
text, type), commandExecution (aggregatedOutput, command,
commandActions, cwd, durationMs, exitCode, id, pluginId,
processId, scriptPath, source, status, type) and fileChange
(changes, diff, id, kind, path, status, type). Record shapes
from source (issue 122):
- What is projected: one rollout line at a time,
task_started,task_completeandturn_abortedintothread_turnsand eachitem_completedintothread_items; every other line type projects nothing (app-server-protocol/src/protocol/thread_history_projection.rs:21-99).history_modelegacyrollouts are not projected (line 1-4). thread_turns:statusinProgress,completed,interruptedorfailed,started_atandcompleted_atin epoch seconds (the turn events' fields; protocol/src/protocol.rs:2189-2192),duration_ms,error_json;rollout_end_ordinalfrom migration 0003 (0003_turn_rollout_positions.sql:1-3; written at thread-store/src/local/thread_history.rs:316-351). The two events are the rollout'stask_startedandtask_completelines (protocol/src/protocol.rs:1407-1418).thread_items:created_at_msis the item'sstarted_at_ms, or the rollout line'stimestampwhen the event has none (thread_history.rs:431-438, thread_history_materialization.rs:245-252);started_at_msandcompleted_at_msfrom migration 0007 (0007_thread_item_lifecycle_timestamps.sql:1-2) are theitem_completedevent's, when the item started and when it completed (core/src/session/mod.rs:2546-2601).item_typeisitem_json.type(0002_thread_items_item_type.sql:1-5).item_jsonisThreadItem, tagged bytypein camelCase (app-server-protocol/src/protocol/v2/item.rs:232-429), converted from the rollout's turn item (869-1063):userMessage(content[]ofUserInput,textinputs withtext; v2/turn.rs:425-459),agentMessage(text, the content blocks concatenated,phase),reasoning(summary[],content[], strings),commandExecution(commanda display string,cwd,status,aggregatedOutput,exitCode,durationMs),fileChange(changes[]ofpath,kind,diff; v2/item.rs:1137-1144),mcpToolCall(server,tool,arguments,result,error),dynamicToolCall(tool,arguments,contentItems),functionCallOutput,plan(text),subAgentActivity,webSearch(id,query,action,results; ext/items/src/web_search.rs:11-22),imageGeneration(revisedPrompt,status,savedPath, and the image itself inresult),sleep(durationMs),hookPrompt,collabAgentToolCall,imageView,enteredReviewMode,exitedReviewMode,contextCompaction.
The two files join the rollout on the thread id, which is the rollout
rows' session_id; the projection has no cwd, so its project path is
taken from the threads row of the same thread. What it survives is
what state_5.sqlite survives; it never holds a legacy rollout's
thread.
logs_2.sqlite¶
logs_2.sqlite logs: Codex's own tracing log, one row per log
event of the app server and the TUI (ts and ts_nanos epoch seconds
and nanoseconds, level, target, feedback_log_body the formatted
message and fields, thread_id, process_uuid;
state/logs_migrations/0002_logs_feedback_log_body.sql:3-16,
file name at
state/src/sqlite.rs:34).
Every target is kept at trace level unless listed
(state/src/log_db.rs:61-63),
and an event's thread_id field fills the thread_id column
(log_db.rs:572-579).
Rows older than 10 days, or past a 64 MiB budget, are pruned
(state/src/runtime/logs_maintenance.rs:10-11),
and Codex's thread delete removes a thread's rows
(state/src/runtime/threads.rs:1170-1173,
called from
thread-store/src/local/delete_thread.rs:139-152).
The one row that marks an upload of a thread: a /feedback report
sent to Sentry (an error-reporting service OpenAI uses for Codex
feedback) logs feedback event uploaded to Sentry at INFO with the
fields thread_id (the reported thread's id, or
no-active-thread-<uuid> when none was open), uploaded_attachments
(a count), attachments_failed (a boolean) and elapsed_ms
(feedback/src/lib.rs:749-755,
298-300),
under the target codex_feedback (the codex-feedback crate). It is
written after the report event is accepted and its attachments are
sent, whether or not the user agreed to upload logs, so
uploaded_attachments is what tells the two apart. The TUI's
log/codex-tui.log does not carry it: its default filter keeps only
codex_core, codex_tui, codex_rmcp_client and
codex_realtime_webrtc
(tui/src/startup_orchestration.rs:817-820).
The parser reads this row and the approval rows below, and no other
row of the database
(Parser plan); a body field may
come in any order, and the first logs migration named the body column
message. Not seen on a real install: the feedback row is read from
a synthetic fixture only.
Codex Desktop writes the per-call permission decisions here and
nowhere else (macOS, build 26.1013.40934, 2026-10-18;
Source and evidence); ts was integer epoch seconds. On Windows (2026-10-15) the
logs table held 2,156 rows from the app server.
- A user's decision in the approval dialog:
codex_app_server::outgoing_messageatINFO,thread_idempty,<- response: CommandExecutionRequestApproval { request_id: Integer(<n>), response: CommandExecutionRequestApprovalResponse { decision: Decline } }(orAccept; at 19:04:01Z and 19:05:00Z on the install), and in the same secondcodex_core::session::handlersatDEBUG,thread_idset,Submission thread_id=<uuid> submission_id=<uuid> operation="exec_approval" ... op=ExecApproval { id: "exec-<uuid>", turn_id: Some("<uuid>"), decision: ... }. A rejected call adds acodex_core::tools::routerERRORrow. Theexec-<uuid>id is theidof the governedCommandExecutionorFileChangeitem (the rollout'sitem_completedline and thethread_itemsrow), not the rollout'scall_id(call_<hex>), so the join to the call is on the item id. The two rows word one decision differently:deniedandapprovedon the handlers row,DeclineandAccepton the response row. The response rows, read in the live database at 19:04Z, were no longer in the copy collected at 00:43Z (thelogstable is pruned: it held 1,000 rows before sign-in and 2,944 at collection, the oldest from 18:31:47Z), while both handlers rows were; the handlers row is the one that lasts. - An automatic decision in the Approve for me mode (auto-review):
codex_guardian_v2::async_scorer::approvalatDEBUG,decide_request{approval_id="<uuid>"}: reviewing approval decision_source="synchronous_assessment" reason=Policy, and rows inside the spanguardian_review{review_id="<uuid>" thread_id=<parent uuid> tool_call_id="exec-<uuid>"}, whosetool_call_idis the governed item's id; the app-server eventsitem/autoApprovalReview/started,guardianWarninganditem/autoApprovalReview/completed. NoCommandExecutionRequestApprovalorExecApprovalrow is written for it; the verdict itself is in the guardian thread's rollout. - A settings change:
operation="thread_settings"withop=ThreadSettings { requested_settings: ThreadSettingsOverrides { approval_policy: ..., approvals_reviewer: ..., sandbox_policy: ..., permission_profile: ... } }, values seenSome(OnRequest),Some(Never),Some(AutoReview),Some(User),Some("managed"),Some("disabled")andNone. The model is not in it; the rollout'sthread_settings_appliedis the model record. - Every app-server request by method (
thread/start,thread/resume,thread/fork,thread/delete,fs/remove,turn/start,command/exec,fs/readFile,gitDiffToRemote,remoteControl/*and more), withapp_server.client_nameandapp_server.client_versionin its span:Codex Desktop26.1013.40934,codex_chatgpt_ios_remote1.2026.279,codex_chatgpt_android_remotedev. A paired phone can run a command on the host (command/exec) and read a file (fs/readFile) outside any turn; those requests are recorded only here.
state_5.sqlite remote control enrollments¶
state_5.sqlite remote_control_enrollments: one row per remote
control enrollment, the link that lets a ChatGPT client drive this
host's app server (codex remote-control; websocket_url,
account_id, app_server_client_name, server_id, environment_id,
server_name, updated_at, and remote_control_enabled from
migration 0037;
state/migrations/0024_remote_control_enrollments.sql:1-10,
0037_remote_control_enrollments_enabled.sql:1-2).
It names the host's enrollment, not a thread. Empty on the Windows
install of Source and evidence. On macOS (build 26.1013.40934,
2026-10-18 and 2026-10-19) enabling remote control in the app
(Settings > Connections > Control this Mac or PC) wrote one row:
websocket_url the relay's websocket address on the chatgpt.com
host (56 characters, no query string), account_id,
app_server_client_name Codex Desktop, server_id
(srv_e_<hex>), environment_id (env_e_<hex>), server_name
the Mac's host name (<name>.local), updated_at and
remote_control_enabled 1. Turning "Allow connections" off kept the
row and set remote_control_enabled to 0 with a new updated_at;
turning it on again reused the row, same ids, with 1. Revoking a
paired phone and pairing a second phone changed nothing in it: the
list of paired devices is held on the server. The row holds no
token (Credentials). The log rows (targets
codex_app_server_transport::transport::remote_control::enroll,
::websocket and ::client_tracker, and the app-server requests):
remoteControl/enable, persisted remote control enrollment: ...,
connected to app-server remote control websocket, status changes
between Connecting, Connected and Disabled (with
connection_end_reason=Disabled), remoteControl/disable,
remoteControl/pairing/start, remoteControl/client/list (polled
while the Connections page is open), remoteControl/client/revoke
(no device name in the row), the event
remoteControl/status/changed when a phone connects, and
client_tracker ... connection closed when it leaves. The first
pairing also wrote two top-level keys of .codex-global-state.json
(.codex-global-state.json). Parser reads the row
(Parser plan).
sqlite/codex-dev.db¶
sqlite/codex-dev.db: written only by the ChatGPT desktop app (observed on Windows, ChatGPT
26.1007.9446.0, 2026-10-15, and on macOS, 26.1007.71326 and 26.1013.40934; not in the Codex source
this document cites, and the app's code is not in the repository, see
Sync, relay and origin rows). It is the app's own thread catalog
and automation store. The collector gathers it through the codex-cli|.codex catalog line, under
codex-cli.
- The thread catalog:
local_thread_catalog, one row per thread the app lists, withlocal_thread_catalog_hosts,_sync_state,_metadataand two scan tables beside it. The local Codex threads are rows withsource_kindvscode,host_idlocal,thread_sourceuserand the thread'scwd,project_idnull even for a thread in a project (macOS, 2026-10-18); the account's ChatGPT conversations are rows withsource_kindchatgptand the host idchatgpt:<workspace uuid>:<user id>(the user id is the 29-character ChatGPT user id, which beginsuser-). Their columns, values and the other catalog tables are in Record schema inchatgpt-desktop.mdand Other stores inchatgpt-desktop.md. Each Codex-mode thread added avscoderow, and the app's delete removed the deleted thread's row. Thechatgpt-desktopparser reads thechatgptrows (since analyzer 0.17.0). Thecodex-cliparser does not read the file: the automation is read fromautomation.toml, and a run would be inautomation_runs, which no real install has shown filled. automations(id,name,prompt,status,next_run_at,last_run_at,cwds,rrule,model,reasoning_effort,target_type,project_id,kind,target_thread_id,execution_environment,account_id,user_idand more) andautomation_runs(thread_id,automation_id,status,thread_title,source_cwd,inbox_title,inbox_summary,archived_user_message,archived_assistant_messageand more), withinbox_items: the scheduled-task store, both empty after a first automation failed to save on Windows. The rows of the saved automations are in Automations.live_visualization_suggestions,local_app_server_feature_enablementandthread_timeline_ledger(host_id,thread_id,sequence,record_id,payload_json), present on Windows and on macOS; on macOS (2026-10-18) they held 0, 1 and 0 rows, the ledger empty through every step.codex_schema_migrations(33 rows withapp_version,build_flavorandbuild_commit), which carries the app version (app_version26.1007.71326,build_flavorprodon Windows).
The chat's text is not in it. Neither conversation_origin nor chatgpt_async_status appears in
the Codex source; conversation_origin tpp shares its token with the package's
.codex-tpp-local-executor-installation-id file name, and what it denotes is not determined
(Record schema in chatgpt-desktop.md).
Automations¶
automations/<slug>/automation.toml: one directory per automation of the ChatGPT desktop app,
named by a slug of the name the user typed (the kebab-case of the task's name on macOS; observed on
Windows, 2026-10-15; not in the Codex source this document cites), beside
automations/.run-jitter-salt (37 bytes, base64-class text). The app calls this kind of task a
heartbeat (kind heartbeat): each run ends with a <heartbeat> block in which the model decides
whether to notify the user. The file is flat TOML (383 bytes on Windows, 356 bytes on macOS
2026-10-18): version integer (1), id string (the slug), kind string (heartbeat), name
string, prompt string (what the automation sends), status string (ACTIVE), rrule string
(an iCalendar recurrence rule, the scheduling syntax of RFC 5545), target_thread_id string (the
thread the automation runs in), created_at and updated_at integers (epoch milliseconds).
Parser reads (issue 119). The rrule
values seen:
RRULE:FREQ=WEEKLY;BYHOUR=9;BYMINUTE=0;BYDAY=MO,TU,WE,TH,FR (Windows, a weekday schedule),
FREQ=WEEKLY;BYHOUR=9;BYMINUTE=0;BYDAY=MO (macOS, 2026-10-15) and
RRULE:FREQ=WEEKLY;BYHOUR=19;BYMINUTE=45;BYDAY=SU,MO,TU,WE,TH,FR,SA for a task the user set to run
daily (macOS, 2026-10-18; the hour is local time). The user reported that scheduled tasks are not
tied to a project and run only on an interval (macOS, build 26.1013.40934).
Creating the automation creates its target thread at once, with a rollout, a thread-writer-locks
lock, a visualizations directory and a session_index.jsonl line under the task's name, its cwd
in a new ~/Documents/Codex/<date>/new-chat/ output directory. On Windows that thread
(rollout-2026-10-15T11-25-49-<uuid>.jsonl, 83,579 bytes, 15:25:50Z, cwd
~\Documents\Codex\2026-10-15\new-chat) had thread_source user, source vscode, originator
Codex Desktop, approval on-request and the model gpt-5.6-terra, which the app chose, and held
one user turn (the automation's prompt), one assistant message and no tool call, task_started to
task_complete in 9,969 ms. On macOS (2026-10-18) its session_meta said nothing about an
automation (source vscode, thread_source user, cwd
~/Documents/Codex/2026-10-18/new-chat); only turn_attribution.turn_trigger marks the turns:
scheduled_task_setup for the setup turn (the agent acknowledges the schedule, no tool call),
automation_heartbeat_run_now for Run now and automation_heartbeat_scheduled for the scheduled
run, whose user messages have no client_id. Both runs reported into that thread, the file's
target_thread_id: the scheduled run's task_complete.last_agent_message ends in a <heartbeat>
block with <decision>DONT_NOTIFY</decision> and a <message>, and the Run now report in a
<heartbeat> block with an <automation_id>. The vendor page Scheduled tasks | ChatGPT
Learn (read 2026-10-18) says "Standalone scheduled
tasks start a new chat for each scheduled run and report results in Scheduled"; neither run here
started a new chat.
The same automation is a codex-dev.db automations row. On Windows: status ACTIVE, kind
heartbeat, rrule as in the file, next_run_at and next_run_nominal_at set (epoch ms),
last_run_at null, cwds an empty JSON array, target_thread_id set, auto_archive 0 and every
other column null (model, reasoning_effort, target_type, project_id,
execution_environment, local_environment_config_path, plugin_template_id,
notification_policy, account_id, user_id, installation_id, legacy_automation_id). On
macOS (2026-10-18): empty strings for target_type, execution_environment,
notification_policy, model and reasoning_effort, cwds [], project_id and
legacy_automation_id null, account_id, user_id and installation_id set, last_run_at the
last run, next_run_nominal_at the scheduled minute and next_run_at that minute moved by a
jitter seeded from .run-jitter-salt. automation_runs (one row per run, linking thread_id and
automation_id) and inbox_items stayed empty on every install: before any run on Windows and on
macOS 2026-10-15, and after a Run now and a scheduled run whose decision was DONT_NOTIFY on
macOS 2026-10-18. A run that decides to notify was not observed.
Ambient suggestions¶
ambient-suggestions/<40 hex>/ambient-suggestions.json: the desktop app's suggestions for a
project root, one directory per root (observed on Windows, 2026-10-15, 139 bytes, and on macOS,
2026-10-18, a second directory when a project was added): currentSuggestionIds (empty list),
generatedAtMs number, projectRoot absolute path, suggestions (empty list). Governed by
desktop.ambient-suggestions-enabled in config.toml. Not read; a file holding suggestions has
not been observed.
.codex-global-state.json¶
.codex-global-state.json (and its .bak): the ChatGPT desktop app's state, one JSON object
(observed on Windows, 2026-10-15, and on macOS, build 26.1013.40934, 2026-10-18 and 2026-10-19;
not in the Codex source this document cites). The key electron-persisted-atom-state holds a JSON
object, not a string, of the app's interface state; it and the top level name threads by id. The
keys that record use of a thread:
prompt-history: agloballist and one list per thread id, each entry{markdown, document}(the prompt as Markdown and as an editor document tree withtypedoc). It is the prompt history of Codex Desktop, which writes nohistory.jsonl.thread-descriptions-v1({<thread id>: string}),thread-user-activity-times-v1(keyed["local","<thread id>"], a number, the last activity in epoch ms) andclient-thread-bindings-v1(client-new-thread:<uuid>to a thread id; on Windows also alocal-chatgpt:<uuid>key, binding a ChatGPT chat).heartbeat-thread-permissions-by-id: per thread idactivePermissionProfile({id, extends}or null),approvalPolicy(a string or the granular object),approvalsReviewer,sandboxPolicy(type,writableRoots,networkAccess,excludeTmpdirEnvVar,excludeSlashTmp): each thread's standing permission state, in camelCase (workspaceWritehere,workspace-writein the rollout). A one-off approval leaves it unchanged (the Demo thread kept one writable root after the approved write to the Desktop), and Full access was never stored in it.agent-mode-by-host-id({local: "guardian-approvals"}),permission-selection-by-host-id:local({kind: "agent-mode", agentMode}) andpreferred-non-full-access-agent-mode-by-host-id: the permission mode the app starts threads in (the scheduled task's new thread started in auto-review). Onlyguardian-approvalswas observed. The mode is kept here, not inconfig.toml.composer-recent-model-configurations-v1([{model, reasoningEffort, serviceTier}], most recent first) andcomposer-model-picker-selection-mode-v1.codex-writing-block-deleted-thread-v1:<thread id>,true: written when the thread is deleted in the app, absent for a live thread (What survives a delete).chatgpt-conversation-resume-tokens-v1(Windows): keyed by a JSON array of the account's user id and a ChatGPT conversation id, each value{expiresAtMs, promptMessageId, token, turnTraceId}, the token 419 characters. No such key had appeared on macOS by the end of sign-in.- Top level:
projectless-thread-ids(a list of thread ids),thread-projectless-output-directoriesandthread-workspace-root-hints({<thread id>: path}),thread-project-assignments,selected-project,app-server-projects-migration-by-host,desktop-first-seen-at-ms, and, from the first remote-control pairing,electron-local-remote-control-environment-id(a string) andcodex-mobile-has-connected-device(a boolean). - Projects and files:
local-projects({<uuid>: {createdAt, id, name, rootPaths, updatedAt}}),recent-workspace-files-v3(one entry per file a thread wrote, keyed["local","client-new-thread:<uuid>","<path>"]; on Windows the last element spells a Windows path aswin32:/mnt/c/...) andcomposer-prompt-drafts-v2(an unsent draft per composer surface). external-agent-import-discovery:claude-code,:claude-coworkand:cursor(detectedAtMs,requestKey,resultwithitemsandunsupportedProjects), written at sign-in: the app looks for Claude Code, Claude Cowork and Cursor state to import (empty results on both installs).- Other keys seen on Windows: the import-discovery keys in the form
external-agent-import-discovery:claude-code,claude-coworkand...:cursor,mcp-global-entrypoints-v2,electron:onboarding-welcome-v2-role-state(completedConversationalOnboardingTaskId,roles,workMode),cloud-pet-artwork-cache(accountId,userId), and, after a Plus subscription and an automation,chatgpt-work-model-settings-v1,fast-mode-personalized-estimate,seen-model-upgrade-listand athread-reference-capability:<uuid>per new thread.
The ChatGPT account's user id appears inside key names: the resume tokens' keys,
chatgpt-work-models-v1/<user id>/<workspace uuid> and the keys of
new-task-execution-by-account-project ({kind: "cloud"}). The file outlives a deleted thread
with its prompts (What survives a delete). The collector flags it
secret: true and collects it by default. Parser reads, as a secondary source
(Parser plan).
Thread locks and shell snapshots¶
thread-writer-locks/ (.coordination.lock and one <thread id>.lock per open thread, 0 bytes)
and shell_snapshots/<thread id>.<unix ns>.sh (macOS, build 26.1013.40934, 2026-10-18): working
files of the running app. The locks of threads that were not open were gone after a quit and
relaunch. At the resume (21:46:13Z) and when the scheduled task was created (23:18:39Z to
23:18:41Z) the app prewarmed a guardian review session, a reviewer warmed up in advance
(startup_prewarm with the model codex-auto-review in logs_2.sqlite), whose id got a 0-byte
lock and no rollout, state_5.sqlite threads row, session_index.jsonl line or
thread_history_1.sqlite rows: a lock is not a session. A shell snapshot (2,135 to 2,299 bytes, 43
lines: a # Snapshot file header, 29 export NAME=... lines, 2 aliases, unalias -a, setopt
lines), named by the thread id and a Unix time in nanoseconds, replays the user's exported
login-shell environment for the sandbox. One is written at each resume and turn of a thread while
older ones are removed: the two most recent per thread remained at the end of the round, and a
quit removed every snapshot of threads not reopened. The exported names on the clean lab VM
included BROWSER_USE_ACCESS_VERIFYING_IDENTITY, CODEX_TASK_WORKSPACE_VERIFYING_IDENTITY,
CODEX_APP_TOOLS_PIPE_PATH, SSH_AUTH_SOCK, HOME, USER and TMPDIR, and no key-like name; on
a workstation an API key the login shell exports is copied here, and the collector flags the files
secret: true (Credentials). The parser reads neither.
Standing grants and approval records¶
From source at
4e7b9e2; not
seen on a real install unless a bullet says so. All are collected by the codex-cli|.codex line;
the rows the parser makes of them are in Permission rows.
rules/default.rules: when the user answers an approval prompt with "approve and remember" (ReviewDecision::ApprovedExecpolicyAmendment, protocol/src/protocol.rs:4269-4304), Codex appends one Starlark line (Starlark is the Python-like configuration language the exec policy is written in)prefix_rule(pattern=["<token>", ...], decision="allow")to<CODEX_HOME>/rules/default.rules, the exec policy file (core/src/exec_policy.rs:54-56, core/src/session/handlers.rs:181-193, core/src/exec_policy.rs:466-490, 874-876, execpolicy/src/amend.rs:65-80), and a network allow or deny answer appendsnetwork_rule(host=..., protocol=..., decision="allow"|"deny", justification=...)(amend.rs:85-124). A line carries no time and no thread; the file's modification time is the last grant. Every other*.rulesfile underrules/is loaded too (exec_policy.rs:681). The file holds no credential. The samerules/directory in a project's.codex/is a project file (Project-local files), loaded as a policy layer like the home's (exec_policy.rs:670-684).config.toml: the standing policy keysapproval_policy,approvals_reviewer,sandbox_modeanddefault_permissions(config/src/config_toml.rs:195, 200, 224, 239);[projects."<path>"] trust_level,trustedoruntrusted(config/src/config_toml.rs:473, core/src/config/mod.rs:2464, protocol/src/config_types.rs:642-645), the one seen on a real install: adding a project folder in the desktop app wrotetrusted(macOS, ChatGPT 26.1013.40934, 2026-10-18); and, for an MCP tool call the user approved for good (ApprovedMcpPolicyAmendment),approval_mode = "approve"undermcp_servers.<server>.tools.<tool>,plugins.<plugin>.mcp_servers.<server>.tools.<tool>or, for a ChatGPT app connector,apps.<connector>.tools.<tool>, in the project's own.codex/config.tomlwhen the server is configured there (mcp_tool_call.rs:2460-2470, 2507-2530) (core/src/mcp_tool_call.rs:2394-2411, 2428-2457, 2472-2490). "Approve for this session" (ApprovedForSession) is kept in memory only and leaves no file.state_5.sqliteguardian_review_feedback(id,thread_id,recordBLOB; migration 0060, not at3e23877, state/migrations/0060_guardian_review_feedback.sql:1-5): one row per automatic review (the desktop app's Approve for me reviewer) that did not allow the action:recordis a JSON objectreviewed_thread_id,reviewed_turn_id,target_item_id,reviewer_thread_id,model,status(denied,invalid_decision,missing_decision,failed,timed_out,aborted),decision(the reviewer's verdict text),action(the reviewed command or patch),instructions,history(the reviewer's own response items) andcontext_omitted(ext/guardian-reviewer/src/feedback.rs:17-30, 57-85, core/src/guardian/feedback.rs:6-44, state/src/runtime/guardian_feedback.rs:33-50). An allowed review writes no row, and nothing is written when feedback is turned off or the thread is ephemeral. Theidis a UUIDv7 made when the record is stored, so its first 48 bits are the capture time in epoch milliseconds, and the table keeps at most 8 records per reviewed thread and 64 in all (guardian_feedback.rs:9-28). The row outlives the reviewer thread; itsthread_idreferencesthreads(id)withON DELETE CASCADE, so it is meant to go with the reviewed thread. The table was present, with no row read, on the macOS install of 2026-10-18 (Source and evidence).- Log lines of the other answers: beside
op=ExecApproval, the core'sSubmissiondebug line (core/src/session/handlers.rs:487-495) printsPatchApproval { id: "<id>", decision: <word> }for anapply_patchapproval, the word as forExecApproval(Format versions) and the id the governedFileChangeitem's, which the app server submits (app-server/src/bespoke_event_handling.rs:1990-1993), andRequestPermissionsResponse { id, scope, strict_auto_review }for a permissions request (protocol/src/protocol.rs:769-776, 803). - Started by another agent with approvals bypassed (Agent Zero source at
ff2a5b9): Agent Zero's Orchestrator plugin, which tells that agent how to drive other coding CLIs on the host or in its container, defaults Codex tobypass_sandbox: trueand runscodex exec --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox(plugins/_orchestrator/default_config.yaml:7-37,plugins/_orchestrator/README.md:86). Such a thread is an ordinary rollout in Codex's own store; the plugin's permission settings are in Parser plan inagent-zero.md.
Other past-use stores¶
From source at
4e7b9e2; not
seen on a real install beyond the goals_1.sqlite and memories_1.sqlite files listed in
macOS, ChatGPT 26.1013.40934. Each is derived from rollouts; which
the parser reads is in Past-use rows and secondary sources.
- Memories, when the
memoriesfeature is on (off by default, features/src/lib.rs:1303-1305):memories_1.sqlite(andmemories_v2_1.sqlite)stage1_outputs, one row per thread withthread_id,raw_memory,rollout_summary,rollout_slugandsource_updated_at(state/memory_migrations/0001_memories.sql:1-12, state/src/sqlite.rs:114-119), and the consolidated files undermemories/in the Codex home (memories/write/src/control.rs:4-8):rollout_summaries/<stem>.md, each headedthread_id:,updated_at:(the thread's last update the summary covers, RFC 3339),rollout_path:,cwd:and, when the thread has one,git_branch:lines, then a blank line and a model-written summary of the thread,raw_memories.mdwith the same header per thread,MEMORY.mdandmemory_summary.md(memories/write/src/lib.rs:39-40, storage.rs:110-136, workspace.rs:87, 101). A thread delete removes itsstage1_outputsrow (state/src/runtime/memory_versions.rs:39-51), and its summary file goes only when the next consolidation prunes the files it no longer keeps (storage.rs:80-108), so until then the summary names a thread whose rollout is gone. Joins onthread_id,cwdandrollout_path. - Thread goals:
goals_1.sqlitethread_goals(thread_id,objective,status,token_budget,created_at_ms,updated_at_ms; state/goals_migrations/0001_thread_goals.sql:1-17), the objective the user set for a thread, also persisted in the rollout asevent_msgthread_goal_updated, whose keys are camelCase (threadId,turnId, andgoalwiththreadId,objective,status,tokenBudget,tokensUsed,timeUsedSeconds,createdAt,updatedAt;statusone ofactive,paused,blocked,usageLimited,budgetLimited,complete; rollout/src/policy.rs:131-139, protocol/src/protocol.rs:4206-4257). Removed with the thread. db-backups/sqlite-<timestamp>-<n>/: a database Codex found corrupt, renamed aside with its sidecars before a fresh one is built (state/src/runtime/recovery.rs:16, 181-200, 241-250). Only the corrupt database and its sidecars move, so the folder holds one ofstate_5.sqlite,logs_2.sqlite,thread_history_1.sqliteor another runtime database. A backed-upstate_5.sqlitekeeps thethreadsrows of its time, deleted threads included.
Format versions¶
- History mode.
session_meta.history_mode(legacydefault,paginated; protocol.rs:775-782). The app server starts non-ephemeral threads paginated when the store supports it (app-server/src/request_processors/thread_processor.rs:1462-1465); the local TUI thread was paginated. Paginated files addordinalanditem_completeditems and drop the legacyuser_message/agent_messageevents. A background legacy-to-paginated rewrite exists behindbackground_paginated_rollout_migration, off by default (features/src/lib.rs:1224-1229, thread-store/src/local/rollout_migration.rs:1-9). - Compression and archive.
.jsonl.zstandarchived_sessions/(Transcript stores); Codex materialises a compressed file back to.jsonlbefore appending to it (compression.rs:104-105), so either form may be present. - Flat layout. Older releases wrote
sessions/rollout-<time>-<uuid>.jsonlwithout date directories (the example in recorder.rs:82); the current lister walks onlyYYYY/MM/DD(list.rs:900-920). - Renames kept for compatibility.
session_metawithoutsession_id,agent_typeforagent_role,turn_started/turn_completealiases,compaction_summaryforcompaction;TurnContextItem.summaryis written only for older readers. -
Not determined at this commit: whether pre-envelope rollouts (no
type/payloadwrapper) exist on disk from very early releases; the current reader rejects lines withouttimestamp. -
Drift from
3e23877to4e7b9e2. Unchanged: the line envelope andRolloutItemset (history/src/lib.rs:215-231),SessionMeta(protocol/src/protocol.rs:3256-3331), theResponseItemvariants, the database file names (state/src/sqlite.rs:41-46),sessions/andarchived_sessions/(rollout/src/lib.rs:86-87) and the/feedbackmarker message (feedback/src/lib.rs:755). Changed: TurnContextItemdroppednetwork(the allowed and denied domain lists),workspace_roots,current_date,timezone,personalityandmulti_agent_mode(present at protocol.rs:3314-3346 at3e23877) and gainedroot_turn_id,disabled_plugin_ids,file_system_sandbox_policy,comp_hash,realtime_activeandcyber_access_program;summaryis optional (protocol.rs:3428-3468). The parser'spermission mode:row printsnetwork=-for a rollout of this version. The network policy is now thenetworkfield of amanagedorexternalpermission_profile(protocol/src/models.rs:424-440), which the composite already prints insideprofile=.task_startedgainedturn_attribution(turn_id,turn_trigger,parent_turn_id,initiating_agent_path,root_turn_id) androot_turn_id(protocol.rs:2302-2325, protocol/src/turn_input.rs:80-86): the field the desktop app's 0.163.0-alpha rollouts already carried and the parser reads.- The
op=ExecApprovaldebug line now has its own format,ExecApproval { id, turn_id, decision: <word> }, the word one ofapproved,approved_with_amendment,approved_for_session,approved_mcp_policy_amendment,approved_with_network_policy_allow,denied_with_network_policy_deny,denied,timed_out,abort(protocol.rs:759-767, 4323-4339), where the derived format printed the enum (Denied { rejection: ... }). The parser's pattern takes the first word either way. event_msgelicitation_abandonedis new and transient (rollout/src/policy.rs:199).- New tables:
guardian_review_feedback(Other stores),thread_read_receipts(state/migrations/0061_thread_read_state.sql:1-8), andthread_turns.root_turn_id(state/thread_history_migrations/0008_thread_turn_root.sql:1). No column the parser selects was removed.
Secrets¶
The credential files under ~/.codex (auth.json, .credentials.json, secrets/,
.sandbox-secrets/) are listed in Credentials. Inside
the stores this document describes:
session_meta.payload.creator_account_idandcreator_user_id, and thestate_5.sqlitethreadscolumns of the same names, name the ChatGPT account and user (Joins and timestamps);token_count.rate_limitsnames the account's plan (event_msg)..codex-global-state.json:chatgpt-conversation-resume-tokens-v1, the ChatGPT conversation resume tokens (a credential); the user's prompts inprompt-history; and the account's ids, in values (cloud-pet-artwork-cacheaccountIdanduserId) and inside key names (the resume tokens' keys,chatgpt-work-models-v1/<user id>/<workspace uuid>and the keys ofnew-task-execution-by-account-project) (.codex-global-state.json).config.tomlcan hold an MCP server'senvand headers and a provider's bearer token (Credentials); the parser prints only the standing permission keys of Permission rows from it.guardian_review_feedbackrecordactionis the reviewed command, which can carry a pasted key, as tool arguments can.shell_snapshots/<thread id>.<unix ns>.shand remote control's server token are in Credentials;state_5.sqliteremote_control_enrollmentsholds no token, only the account id and the host name.
Parser plan¶
CodexParser wants
^\.codex/(?:archived_)?sessions/(?:.*/)?rollout-[^/]*\.jsonl(?:\.zst)?$,
.codex/history.jsonl, the stores of Other stores the table below names, and,
source-derived, .codex/config.toml, ^\.codex/rules/[^/]+\.rules$,
^\.codex/memories/rollout_summaries/[^/]+\.md$ and
^\.codex/db-backups/[^/]+/state_5\.sqlite$. Until session_meta is read, the session id is
the thread id in the file name: the last UUID, or the first of a reverted
rollout's <thread id>_<rollout id>. The parser reads the whole file before
emitting rows, so that an item_completed line can be checked against every
response_item id and call_id in the file.
| Column | Source |
|---|---|
| timestamp_utc | line timestamp; session_meta.payload.timestamp for the start, subagent and fork rows; item_completed started_at_ms / completed_at_ms for the rows built from a turn item; history ts (s) |
| session_id | the file's first session_meta.payload.id, else its session_id, else the file-name thread id; a later session_meta never changes it; history session_id |
| project_path | first session_meta.payload.cwd, replaced by each turn_context.payload.cwd |
| git_branch | first session_meta.payload.git.branch |
| model | turn_context.payload.model (empty before the first turn_context) |
| turn_type | coverage table below |
| tool_name | name; shell for local_shell_call and CommandExecution, web_search for web_search_call and web-search turn items, tool_search, <server>.<tool> for McpToolCall, apply_patch for FileChange |
| tool_use_id | call_id (call falls back to id); a turn item's id |
| text | message: content[].text joined, prefixed <role>: for developer; user-role context context: <kind>: <text>; tool_use: arguments or input, action.command joined, action.query, arguments JSON; tool_result: output string or [].text joined, prefixed [exit N] when the matching CommandExecution item has a non-zero exit_code; thinking: summary[].text, else content[].text |
| Record | Row |
|---|---|
first session_meta |
system ("session start: originator version provider cwd", then account=<creator_account_id> user=<creator_user_id> with only the keys present, issue 118); plus, right after it and at the same time, system git remote: <git.repository_url> when the session_meta records one (issue 253); plus system subagent <id> of <parent> [agent_path= agent_role= agent_nickname=] when parent_thread_id, source.subagent.thread_spawn.parent_thread_id, or a session_id other than id on a non-fork names a parent; plus system forked from <id> for forked_from_id |
later session_meta |
system copied from ancestor: session start ... id= cwd= when its id differs; none when it repeats the file's own |
turn_context |
sets project and model; system model: <model> at the first one that names a model and model: <old> -> <new> at each later one whose model differs; system permission mode: approval=<approval_policy> sandbox=<sandbox_policy> profile=<permission_profile> network=<network> reviewer=<approvals_reviewer> profile_id=<active_permission_profile.id> at the first one with any of the fields and permission mode: <old> -> <new> on every change to the composite (a missing field is -, an object compact JSON); both at the line's timestamp, before the turn's rows, keyed per session |
event_msg thread_settings_applied (Codex Desktop) |
the same model: and permission mode: tracking as turn_context, at the event's timestamp, so a change the user makes between turns (a model switch, Approve for me, Full access and its revert) is dated when it was made rather than at the next turn. The event is the thread's settings, not a turn's: it carries model, approval_policy, approvals_reviewer and active_permission_profile, no sandbox_policy, and a permission_profile without the roots a turn adds, so only the three thread-level fields come from it; sandbox and profile stay as the last turn_context had them, or read - once the access level changed until the next turn_context, and an event before the thread's first turn_context is folded into that turn's row (issue 160) |
response_item with metadata.inherited_user_message |
none (copied from the parent thread) |
response_item message user |
per content_item_kinds run: user for user.*, unknown or empty kinds; system context: <kind>: ... for any other kind; without a kinds list as long as content, per block: system context: <marker name>: ... for a block that is wholly one of the harness wrappers below, else user. In a subagent file the first prompt is system subagent task: ...; later ones stay user (below) |
response_item message assistant / developer |
assistant / system (developer: ...); empty text skipped |
response_item function_call, custom_tool_call, local_shell_call, web_search_call, tool_search_call |
tool_use |
response_item function_call_output, custom_tool_call_output, local_shell_call_output, tool_search_output (tool names) |
tool_result |
response_item agent_message, inter_agent_communication |
system agent message <author> -> <recipients>: ... (subagent task: for a subagent's first prompt); an inter_agent_communication whose id a response item carries is skipped |
response_item image_generation_call |
system image generation: <revised_prompt> status=... |
response_item reasoning |
thinking with --include-thinking when it has text, else skipped |
response_item additional_tools, compaction, context_compaction, configuration_update |
skipped (no readable content) |
event_msg task_started, task_complete |
system; task_started turn=<id> as before (so the thread_history_1.sqlite copy still pairs with it), followed by turn trigger: <turn_trigger> turn=<id> when turn_attribution.turn_trigger is set (values in event_msg; a remote_* turn came from a paired phone) |
event_msg task_complete of a guardian review thread (thread_source guardian_review) |
system permission decision: <outcome> (auto_review risk_level=<risk_level> user_authorization=<user_authorization>) ... from the JSON verdict in last_agent_message, naming the reviewer thread and carrying the rationale in full, at the line's timestamp, with session_id the governed thread (session_meta.parent_thread_id) |
event_msg thread_goal_updated |
system thread goal: status=<goal.status> turn=<turnId> objective: <goal.objective>, - for a missing status or turn (source-derived) |
event_msg turn_aborted |
system turn aborted: <reason> [error message] |
event_msg thread_rolled_back |
system rolled back <n> turns; earlier rows are not changed |
event_msg item_completed UserMessage, AgentMessage, Reasoning, HookPrompt, ContextCompaction |
skipped (repeat a response item or compacted) |
event_msg item_completed, other items whose id a response item carries as id or call_id |
skipped; a CommandExecution exit code still marks the call's tool_result |
event_msg item_completed CommandExecution, McpToolCall, DynamicToolCall, FileChange, WebSearch, Extension web.search not carried |
tool_use + tool_result on the item id |
event_msg item_completed FunctionCallOutput not carried |
tool_result |
event_msg item_completed Plan, SubAgentActivity, image generation, clock.sleep, others not carried |
system (plan: ..., subagent activity: ..., image generation: ..., sleep: N ms, item <type>: <JSON>) |
event_msg legacy user_message, agent_message, agent_reasoning, and the rest |
skipped |
compacted |
system compaction summary: <message>, or compaction: replacement history of N items |
realtime_item transcript_segment |
user or assistant by role |
realtime_item other types |
system realtime: <type> <JSON> |
token_usage_record, world_state, inter_agent_communication_metadata, retained_context, security_risk_score |
skipped |
bad line, in a rollout or in history.jsonl |
one system row per bad line at the end of the file's rows, parser: unparseable line (<decoder reason>): <the line in full>, source_line the bad line, session_id the rollout's session (empty for history.jsonl) |
| cut or corrupt zstd frame | one system row parser: zstd: <message> at the end, source_line the line after the last one decoded |
history.jsonl line |
user |
state_5.sqlite threads row (secondary) |
system thread record: <id> title=<title> rollout=<rollout_path>, then name=, branch=, model= (the last turn's) and archived when set, at created_at_ms (else created_at); then system git remote: <git_origin_url> at the same time when the column is set, the same text as the rollout's row (issue 253); then first_user_message as user with no timestamp, system subagent task: ... in a subagent's thread |
thread_history_1.sqlite thread_turns row (secondary) |
system task_started turn=<id> at started_at; task_complete turn=<id> duration_ms=<n> at completed_at for a completed or failed turn; an interrupted turn has no closing row (its reason is only in the rollout) |
thread_history_1.sqlite thread_items row (secondary) |
userMessage user, agentMessage assistant, reasoning thinking (opt-in), at completed_at_ms; hookPrompt, contextCompaction none; any other item renamed to the turn-item form and rendered as the item_completed rows above, at started_at_ms and completed_at_ms (else created_at_ms); an item_json that does not decode to a JSON object is one system row per record at the end of the file's rows, parser: unparseable record <rowid> (<reason>): <item_json as stored>, source_line the rowid, session_id and project_path the record's thread's (issue 184) |
automations/<slug>/automation.toml (Codex only) |
one system row scheduled task: <id> name=<name> kind=<kind> status=<status> rrule=<rrule> target_thread=<target_thread_id> created=<created_at> updated=<updated_at> (from <file>, file mtime) prompt: <prompt> at the file's modification time, session_id the target thread; an unreadable file is one system row (issue 119) |
logs_2.sqlite logs row codex_core::session::handlers op=ExecApproval or codex_app_server::outgoing_message CommandExecutionRequestApproval (Codex Desktop) |
system permission decision: <decision> exec <item id> turn=<turn id> (from logs_<N>.sqlite logs, ts) at ts from the handlers row (denied, approved seen), session_id the row's thread id, tool_use_id the governed item's id (exec-<uuid>, Other stores); a response row (Decline, Accept) with no handlers row within two seconds is permission decision: <decision> request <n> (from logs_<N>.sqlite logs, ts) with no session (issue 160) |
logs_2.sqlite logs row codex_core::session::handlers op=PatchApproval |
system permission decision: <decision> patch <item id> (from logs_<N>.sqlite logs, ts) at ts, session_id the row's thread id, tool_use_id the governed FileChange item's id (source-derived) |
state_5.sqlite guardian_review_feedback row |
system at the id's UUIDv7 time, session_id reviewed_thread_id, tool_use_id target_item_id: for a denied record whose decision is the reviewer's JSON verdict, the guardian review thread's text permission decision: <outcome> (auto_review risk_level=<risk_level> user_authorization=<user_authorization>) exec <target_item_id> reviewer <reviewer_thread_id>: <rationale>, so the two pair; for any other, permission decision: <status> (auto_review feedback model=<model>) exec <target_item_id> turn=<reviewed_turn_id> reviewer <reviewer_thread_id>: <decision> action: <action>, - for an empty value; a record that is not a JSON object is parser: unparseable record <rowid> (<reason>): <record> (source-derived) |
config.toml (the home's, or a project's .codex/config.toml) |
one system row per standing key, permission setting: <key>=<value> (from .codex/config.toml, file mtime), for approval_policy, approvals_reviewer, sandbox_mode, default_permissions, projects.<path>.trust_level (project_path the path) and each approval_mode under mcp_servers.<server>.tools.<tool>, plugins.<plugin>.mcp_servers.<server>.tools.<tool> and apps.<connector>.tools.<tool>, at the file's modification time, session_id empty; no other key is printed; an unreadable file is one parser: unreadable config.toml: <reason> row (source-derived) |
rules/*.rules |
one system row per Starlark statement, permission setting: <statement> (from .codex/rules/<file>, file mtime), the statement joined onto one line with comments dropped, at the file's modification time, source_line its first line, session_id empty; a statement still open at the end of the file is parser: unterminated rule: <its lines> (source-derived) |
memories/rollout_summaries/<stem>.md |
one system row memory summary: thread <thread_id> rollout=<rollout_path> (from <file>, updated_at): <summary> at updated_at, session_id the thread, project_path cwd, git_branch git_branch; a file with no thread_id: header is parser: memory summary has no thread_id header (source-derived) |
db-backups/sqlite-<time>-<n>/state_5.sqlite (secondary) |
the rows of state_5.sqlite above, from the copy (source-derived) |
state_5.sqlite remote_control_enrollments row |
system remote control: enabled=<0\|1> server=<server_id> environment=<environment_id> host=<server_name> client=<app_server_client_name> account=<account_id> at updated_at, session_id empty (issue 160) |
.codex-global-state.json (secondary, Codex Desktop) |
user for each prompt-history entry of a thread, with an empty timestamp_utc (matched on text and session), session_id the thread id; system session deleted: <id> for each codex-writing-block-deleted-thread-v1:<id> key and system permission mode (standing): ... for each heartbeat-thread-permissions-by-id entry, at the file's modification time; system thread description: ... for each thread-descriptions-v1 entry; one system remote control: mobile device connected=<bool> environment=<id> row from the two top-level keys; one system scratch directory: <path> (projectless thread <id>) (from <file> thread-projectless-output-directories, file mtime) row for each thread-projectless-output-directories entry, session_id the thread id and project_path the path itself, so inventory.json lists the directory under scratch_directories (issue 149); a file that does not decode is one parser: row (issue 160) |
Permission rows¶
Permission state is the per-turn turn_context policy above and, on Codex Desktop, every
thread_settings_applied event and the standing per-thread entries of .codex-global-state.json.
The rollout holds no per-call decision: approval requests and their answers are transient events
that the rollout never stores
(policy.rs:160-223),
so a rollout shows what the policy allowed, not what the user approved. Codex Desktop logs the
user's answers in logs_2.sqlite and records an automatic answer as the verdict of a guardian
review thread (macOS, build 26.1013.40934, 2026-10-18; Other stores); the parser
yields a permission decision: row from each, joined to the governed call on its item id. The
approval dialog of that build offered no standing "always allow" grant for a command.
The standing grants and the other approval records of
Standing grants and approval records give the rows of the
table above, source-derived; not observed on any real install. Each statement of a rules/*.rules
file and each standing key of config.toml (the policy defaults, a project's trust_level, a
tool's approval_mode) is one permission setting: row at the file's modification time, which is
the time of the last grant, not of each; a project's own .codex/config.toml and .codex/rules/
give the same rows with that project as project_path. On Python 3.10, which has no tomllib,
config.toml is read for its single-line values under every table header, which holds every key
these rows need. Each PatchApproval log line is a permission decision: ... patch <item id> row
joined to the apply_patch call on the item id, like ExecApproval. Each
guardian_review_feedback record is a permission decision: row at its capture time, joined to the
governed call on target_item_id: a denied verdict reads exactly as the guardian review thread's
row, so dedup.deduplicate keeps one of the two when both exist, and a review that failed, timed
out or gave no readable verdict, which the review thread does not record as a verdict, is its own
row naming the status and the reviewed action. RequestPermissionsResponse lines are not read:
their scope is a debug print whose shape the source does not fix.
Model rows¶
The model is recorded per turn, in turn_context.model, and on Codex Desktop also at each change,
in thread_settings_applied; there is no per-message model, so the model rows follow the turns and
the settings events.
Past-use rows and secondary sources¶
Sources, by role (issue 122). The rollouts
are the primary store: Codex writes every other file from them. history.jsonl, state_5.sqlite,
thread_history_1.sqlite and Codex Desktop's .codex-global-state.json
are secondary sources, read on every run; the
timeline drops a row of theirs that a rollout row of the same session
repeats (same turn_type and text, time within 2 seconds, or any time
for a row with none) and keeps the rest. How each joins:
| Source | session_id | project_path | Time |
|---|---|---|---|
history.jsonl |
session_id |
none | ts, whole seconds |
state_5.sqlite thread record |
threads.id |
threads.cwd |
created_at_ms, the thread's creation |
state_5.sqlite first prompt |
threads.id |
threads.cwd |
none: matches on text and session |
thread_history_1.sqlite |
thread_id |
the same thread's threads.cwd from the state_5.sqlite beside it, else empty |
the item's or turn's own, as the rollout row it repeats |
.codex-global-state.json prompt history |
the thread id in the key | none | none: matches on text and session |
The projection's rows take the times of the rollout rows they repeat:
a message at completed_at_ms, when the response item is written; a
tool item at its start and end, as the rollout reader stamps an
item_completed item; a turn at its started_at and completed_at,
under a second from the task_started and task_complete lines. The
fixture pairs all of them within the default 2 seconds. A tool call
made through a function call has a rollout tool_use text of the
model's arguments ({"cmd": ...} for exec_command, the patch for
apply_patch) and a projection text of the parsed command or the
changed paths, so the two never pair on text; dedup.deduplicate pairs
tool_use and tool_result rows that both carry a tool_use_id on
that id instead (analyzer 0.13.0). Known misses, each shown twice: a web
search longer than 2 seconds whose rollout also holds the
web_search_call response item, stamped at the end; on Open
Interpreter, an agentMessage, stamped at its start because the fork
has no migration 0007. A thread's first prompt is in three secondary
sources (history.jsonl, first_user_message, the projection's
userMessage); one pairs with the rollout's prompt, and
dedup.deduplicate collapses the other two onto that copy, the undated
first_user_message included, so only the rollout's row is kept
(fixture: FIXTURE_DEDUPLICATED in tests/test_cli.py).
The stores of Other past-use stores, source-derived; not observed on any real install:
db-backups/sqlite-<time>-<n>/state_5.sqlite: secondary, read asstate_5.sqliteis; its rows that the live database or a rollout repeats are dropped, and a deleted thread's record and first prompt are kept.memories/rollout_summaries/<stem>.md: onememory summary:row per file, joined onthread_idandcwd, atupdated_at. It is read on every run as a source of its own, not a secondary one: the summary is the model's text about the thread, which repeats no event, so nothing could pair with it, and marking it recovered would mislabel every live thread. It outlives a thread delete until the next consolidation.thread_goal_updated: primary, in the rollout, onethread goal:row per event.- Not read:
raw_memories.md(one file for many threads, the raw memories' body has no fixed shape),MEMORY.mdandmemory_summary.md(consolidated across threads),memories_1.sqlitestage1_outputsandgoals_1.sqlitethread_goals(each removed with the thread, repeating what the summary file and the goal events hold), and the other databases underdb-backups/.
Sync, relay and origin rows¶
Codex has no session sync. The thread store is local only: the two backends are Local (rollouts
and SQLite) and InMemory
(core/src/config/mod.rs:599-607),
and the old experimental_thread_store_endpoint for a remote store is now refused at load
(3251-3254).
The thread metadata sync of Joins and timestamps copies rollout facts into
state_5.sqlite and calls no server
(thread-store/src/thread_metadata_sync.rs:34-38).
codex resume reopens a local rollout
(cli/src/main.rs:206-207),
and the app server's thread/* methods have no share or upload method. What does leave the host,
beyond the model API call:
/feedback("send logs to maintainers"; tui/src/slash_command.rs:73, 93) sends a report to OpenAI's Sentry project (feedback/src/lib.rs:60-61). The TUI asks "Upload logs?" and lists the files (tui/src/bottom_pane/feedback_view.rs:252, 282-285); on yes the open thread's rollout (tui/src/app/background_requests.rs:623-627) and the rollouts of its subagent threads are attached (app-server/src/request_processors/feedback_processor.rs:179-196), packed intorollouts.tar.gzin a system temporary directory (feedback/src/lib.rs:840-853). At4e7b9e2a report with logs also attaches the thread's failed automatic reviews asauto-review-failures.jsonl. On no, the report carries the category, the note and tags only, and the TUI says "Feedback recorded (no logs)" (feedback_view.rs:54-58). Opt-in per report; the whole flow is on by default and turned off by[feedback] enabled = falseinconfig.toml(config/src/types.rs:233-236, core/src/config/mod.rs:4498-4502, feedback_processor.rs:62-66). The marker is thelogs_2.sqliterow of Other stores. The parser yields onesystemrow per such log row (targetcodex_feedbackand the messagefeedback event uploaded to Sentry; the othercodex_feedbackrows report attachment failures and are not uploads),session synced: feedback report uploaded to Sentry, attachments <uploaded_attachments> failed <attachments_failed> (from logs_2.sqlite logs, ts),-for a field the body lacks, attsplusts_nanos, withsession_idthe body'sthread_id(empty for ano-active-thread-id) and the project and branch of that thread'sthreadsrow; the parser setsrecords_sync, so a thread without the row issyncednoinsessions.jsonl. A report with 0 attachments sent no rollout. Fixture only: no real install has shown the row. The row survives the rollout's loss outside Codex for up to 10 days; Codex's own thread delete removes it with the thread, Codex offers no delete of the Sentry copy, and nothing local records one.codex cloud(experimental; cli/src/main.rs:227-229) creates a Codex Cloud task, a run on OpenAI's servers against a GitHub branch: the typed prompt, the environment id and the branch, plus a local diff only whenCODEX_STARTING_DIFFis set (cloud-tasks-client/src/http.rs:332-372). It sends no local thread; the task's transcript is server-side, andcodex applyfetches a task's diff back (chatgpt/src/get_task.rs:37-38). Its only local trace is anerror.logthat it appends to in the directory it was started from, not under~/.codex(cloud-tasks/src/util.rs:18-28, cloud-tasks-client/src/http.rs:911-920), with lines[<RFC 3339 time>] new_task: created id=<task id> env=<env id> prompt_chars=<n>(http.rs:374-381), the GitHub origins it matched (cloud-tasks/src/env_detect.rs:51) and theaccount_idit authenticated as (cloud-tasks/src/lib.rs:103). No catalog line collects that file; no row is proposed until it is.codex remote-control(experimental; cli/src/main.rs:176-177, cli/src/remote_control_cmd.rs:53-62) enrolls the host's app server with the ChatGPT backend (wham/remote/control/environments; app-server-transport/src/transport/remote_control/clients.rs:268-270) so a ChatGPT client can drive it over a websocket. Threads stay in the local store and the remote client reads them live through the app server. Observed with the ChatGPT app's remote connections (Settings > Connections > Control this Mac or PC; build 26.1013.40934, 2026-10-18 and 2026-10-19, Source and evidence), it leaves two records: the host-levelremote_control_enrollmentsrow of Other stores, which the parser yields as aremote control:row, and a per-turn mark,turn_attribution.turn_triggerremote_iosorremote_androidon a turn sent from a paired phone, which the parser'sturn trigger:row carries; the phone's own connection is inlogs_2.sqlite. The vendor page Remote connections | ChatGPT Learn (read 2026-10-18) says "Remote connections let you access work running on another device or machine", "Your phone sends prompts, approvals, and follow-up messages to ChatGPT" and "Repository files and local documents come from the connected host"; it does not say whether a transcript is copied to the account. The relay carries control, not a copy of the thread, so the parser marks no session synced or relayed for it.--remote <ADDR>points the TUI at an app server the user runs elsewhere (cli/src/main.rs:962-974). The rollout is written on that server's host, but the TUI still appends each prompt to the localhistory.jsonl(tui/src/app/thread_routing.rs:557-566), so ahistory.jsonlsession with no local rollout may be such a thread. Not a vendor upload.- Analytics go to
<chatgpt base URL>/codex/analytics-events/events(analytics/src/client.rs:154) as events keyed by thread id (analytics/src/events.rs:257); no prompt or reply text field was found (searchedprompt,text,user_messageinanalytics/src). Turned off by[analytics] enabled = false(config/src/types.rs:223-228). OpenTelemetry export, which can carry prompts and replies, is off by default and goes only to an exporter the user configures (config/src/types.rs:597-611, 647-657). Image attachments are kept inline in the rollout; the one attachment store uploads nothing (attachment-store/src/lib.rs:160-170).
The ChatGPT desktop app's own sync state is outside this source: its
client code is not in the repository (searched
chatgpt-conversation-resume-tokens, resume-tokens,
conversation_origin, chatgpt_async_status, codex-dev.db,
local_thread_catalog and electron-persisted-atom-state across the
clone; the only matches are unrelated token-usage replay code). The
chatgpt rows of codex-dev.db local_thread_catalog and the
resume tokens in .codex-global-state.json are the app's index of
ChatGPT conversations that live on the server, copied one way, account
to app, as a sign-in snapshot; the chatgpt-desktop parser reads them
(Parser plan in chatgpt-desktop.md).
Whether a local Codex thread is ever uploaded as a ChatGPT conversation
was not determined from source; on macOS (build 26.1013.40934,
2026-10-18) it was not: the app's Codex-mode threads did not appear in
the Codex area of chatgpt.com (a user report, no listing), and the
delete dialog offered no remote copy to remove. The vendor page
Codex Cloud (read 2026-10-18)
describes server-side tasks, where "Each task has its own workspace and
can keep working while your computer is asleep", and says nothing about
uploading local threads. So the parser marks no Codex-mode thread
synced.
Codex records no relay and no remote-origin session, at 3e23877 and at 4e7b9e2.
Scratch directory rows¶
Codex Desktop's projectless output directory is a scratch directory: the parser yields a
scratch directory: row for each thread-projectless-output-directories entry of
.codex-global-state.json (the table above), so inventory.json lists the directory under
scratch_directories (issue 149).
A rollout cwd under <home>/.codex/worktrees is classed as a scratch directory by the catalog
rule (source at 4e7b9e2); one under a configured git-worktree-root is not, and no row marks it.
The worktree's codex-thread.json (version, ownerThreadId) sits in the source repository's git
directory for that worktree
(worktree/src/metadata.rs:19-27,
88-91),
outside every catalog path.
Deletion rows¶
The desktop app's deletion marker already gives a session deleted: row. A delete through
thread/delete (the TUI, the app server) leaves no marker; what it removes and what survives it is
in What survives a delete, and the survivors that matter here are
history.jsonl, the memory rollout summaries and the db-backups/ copies of state_5.sqlite,
which the parser reads; none of them records the delete itself.
Bad-line rows¶
A bad line in a rollout or in history.jsonl, a cut or corrupt zstd frame, a
thread_history_1.sqlite row whose item_json does not decode, a guardian_review_feedback
record that is not a JSON object and a rules statement still open at the end of its file each
give the rows in the table above (the last two source-derived).
Until issue 184 a thread_history_1.sqlite
row with an unreadable item_json gave one summary row
(parser: <n> thread_items row(s) with unreadable item_json, first at rowid <n>); it is now one
row per record with the value in full, as the table above gives (fixture only, no real install has
shown a corrupt row).
Fixtures¶
Expected rows from the sample below: system session start, system
task_started, system developer: ..., system
context: environments.environment_context: ..., user "run the tests",
tool_use exec_command (call_1), tool_result [exit 1] 1 failing
(the CommandExecution item has id call_1, so it adds only the exit
code), thinking "one test fails" with --include-thinking, assistant,
system task_complete, system
agent message /root -> /root/tester: rerun only the failing test, and
a system row carrying the cut last line in full. The UserMessage item and the
token_usage_record give no row.
Sample rollout, paginated shape, at
.codex/sessions/2026/10/02/rollout-2026-10-02T11-00-00-0199b2c3-1111-7aaa-8bbb-000000000001.jsonl:
{"timestamp":"2026-10-02T09:00:00.000Z","ordinal":0,"type":"session_meta","payload":{"session_id":"0199b2c3-1111-7aaa-8bbb-000000000001","id":"0199b2c3-1111-7aaa-8bbb-000000000001","timestamp":"2026-10-02T09:00:00.000Z","cwd":"/srv/proj","originator":"codex-tui","cli_version":"0.150.0","source":"cli","thread_source":"user","model_provider":"openai","history_mode":"paginated","git":{"commit_hash":"0a1b2c3d","branch":"main"}}}
{"timestamp":"2026-10-02T09:00:01.000Z","ordinal":1,"type":"event_msg","payload":{"type":"task_started","turn_id":"0199b2c3-2222-7aaa-8bbb-000000000001","started_at":1790931601,"model_context_window":272000,"collaboration_mode_kind":"default"}}
{"timestamp":"2026-10-02T09:00:01.100Z","ordinal":2,"type":"response_item","payload":{"type":"message","id":"m1","role":"developer","content":[{"type":"input_text","text":"<permissions instructions>sandboxed</permissions instructions>"}]}}
{"timestamp":"2026-10-02T09:00:01.200Z","ordinal":3,"type":"turn_context","payload":{"turn_id":"0199b2c3-2222-7aaa-8bbb-000000000001","cwd":"/srv/proj","approval_policy":"on-request","sandbox_policy":{"type":"workspace-write"},"model":"gpt-5.5-codex","summary":"auto"}}
{"timestamp":"2026-10-02T09:00:01.300Z","ordinal":4,"type":"response_item","payload":{"type":"message","id":"m2","role":"user","content":[{"type":"input_text","text":"<environment_context>\n<cwd>/srv/proj</cwd>\n</environment_context>"}],"internal_chat_message_metadata_passthrough":{"turn_id":"0199b2c3-2222-7aaa-8bbb-000000000001","content_item_kinds":["environments.environment_context"]}}}
{"timestamp":"2026-10-02T09:00:01.400Z","ordinal":5,"type":"response_item","payload":{"type":"message","id":"m3","role":"user","content":[{"type":"input_text","text":"run the tests"}],"internal_chat_message_metadata_passthrough":{"turn_id":"0199b2c3-2222-7aaa-8bbb-000000000001","content_item_kinds":["user.text"]}},"metadata":{"client_authored":true,"user_input_order":0}}
{"timestamp":"2026-10-02T09:00:01.500Z","ordinal":6,"type":"event_msg","payload":{"type":"item_completed","thread_id":"0199b2c3-1111-7aaa-8bbb-000000000001","turn_id":"0199b2c3-2222-7aaa-8bbb-000000000001","item":{"type":"UserMessage","id":"u1","content":[{"type":"text","text":"run the tests","text_elements":[]}]},"started_at_ms":1790931601500,"completed_at_ms":1790931601500}}
{"timestamp":"2026-10-02T09:00:03.000Z","ordinal":7,"type":"response_item","payload":{"type":"function_call","id":"fc1","name":"exec_command","arguments":"{\"cmd\":\"npm test\"}","call_id":"call_1"}}
{"timestamp":"2026-10-02T09:00:06.000Z","ordinal":8,"type":"response_item","payload":{"type":"function_call_output","call_id":"call_1","output":"1 failing"}}
{"timestamp":"2026-10-02T09:00:06.100Z","ordinal":9,"type":"event_msg","payload":{"type":"item_completed","thread_id":"0199b2c3-1111-7aaa-8bbb-000000000001","turn_id":"0199b2c3-2222-7aaa-8bbb-000000000001","item":{"type":"CommandExecution","id":"call_1","command":["npm","test"],"cwd":"/srv/proj","parsed_cmd":[],"source":"agent","status":"failed","aggregated_output":"1 failing","exit_code":1},"started_at_ms":1790931603000,"completed_at_ms":1790931606000}}
{"timestamp":"2026-10-02T09:00:07.000Z","ordinal":10,"type":"response_item","payload":{"type":"reasoning","id":"rs1","summary":[{"type":"summary_text","text":"one test fails"}],"encrypted_content":"gAAAA"}}
{"timestamp":"2026-10-02T09:00:08.000Z","ordinal":11,"type":"response_item","payload":{"type":"message","id":"m4","role":"assistant","phase":"final_answer","content":[{"type":"output_text","text":"One test fails in src/a.test.ts."}]}}
{"timestamp":"2026-10-02T09:00:08.100Z","ordinal":12,"type":"token_usage_record","payload":{"thread_id":"0199b2c3-1111-7aaa-8bbb-000000000001","turn_id":"0199b2c3-2222-7aaa-8bbb-000000000001","session_id":"0199b2c3-1111-7aaa-8bbb-000000000001","root_turn_id":"0199b2c3-2222-7aaa-8bbb-000000000001","response_id":"resp_1","usage":{"input_tokens":900,"cached_input_tokens":0,"output_tokens":40,"reasoning_output_tokens":10,"total_tokens":940}}}
{"timestamp":"2026-10-02T09:00:08.200Z","ordinal":13,"type":"event_msg","payload":{"type":"task_complete","turn_id":"0199b2c3-2222-7aaa-8bbb-000000000001","last_agent_message":"One test fails in src/a.test.ts.","started_at":1790931601,"completed_at":1790931608,"duration_ms":7200}}
{"timestamp":"2026-10-02T09:00:09.000Z","ordinal":14,"type":"inter_agent_communication","payload":{"author":"/root","recipient":"/root/tester","other_recipients":[],"content":"rerun only the failing test","trigger_turn":true}}
this line is cut mid-wri
history.jsonl:
{"session_id":"0199b2c3-1111-7aaa-8bbb-000000000001","ts":1790931601,"text":"run the tests"}
Source-derived samples (codex at 4e7b9e2), each giving the row of the table above:
rules/default.rules:prefix_rule(pattern=["git", "status"], decision="allow")andnetwork_rule(host="example.com", protocol="https", decision="deny", justification="no").config.toml:approval_policy = "on-request",[projects."/srv/proj"]trust_level = "trusted",[mcp_servers.github.tools.create_issue]approval_mode = "approve".- A
thread_goal_updatedline:{"timestamp":"2026-10-02T09:00:01.500Z","type":"event_msg","payload":{"type":"thread_goal_updated","threadId":"0199b2c3-1111-7aaa-8bbb-000000000001","turnId":"t1","goal":{"threadId":"0199b2c3-1111-7aaa-8bbb-000000000001","objective":"keep the tests green","status":"active","tokensUsed":0,"timeUsedSeconds":0,"createdAt":1790931601,"updatedAt":1790931601}}}. - A
logs_2.sqlitebody:Submission thread_id=<thread> submission_id=<uuid> operation="patch_approval" op=PatchApproval { id: "<item id>", decision: approved_for_session }. - A
guardian_review_feedbackrow:ida UUIDv7,record{"reviewed_thread_id":"<thread>","reviewed_turn_id":"t3","target_item_id":"<item id>","reviewer_thread_id":"<review thread>","model":"codex-auto-review","status":"timed_out","decision":null,"action":"rm -rf build","action_truncated":false,"instructions":null,"history":[],"context_omitted":false}. memories/rollout_summaries/<stem>.md: the linesthread_id: <thread>,updated_at: 2026-10-02T09:00:10+00:00,rollout_path: <path>,cwd: /srv/proj,git_branch: main, a blank line, then the summary.
Injected user-role context¶
Rollouts without content kinds are classified per content block by the wrappers Codex itself
recognises as injected user-role context
(core/src/context/contextual_user_message.rs:22-37).
As in Codex's own check, the block must begin with the open marker after leading whitespace and end
with the close marker, ignoring ASCII case
(context-fragments/src/fragment.rs:116-130),
so a tag a person types inside a prompt leaves it user:
| Marker name | Open … close | Source |
|---|---|---|
agents_md_instructions |
# AGENTS.md instructions … </INSTRUCTIONS> |
core/src/context/user_instructions.rs:23-25 |
environment_context |
<environment_context> … </environment_context> |
protocol/src/protocol.rs:120-121, core/src/context/world_state/environment.rs:524-529 |
agent_message_board_notification |
<agent_message_board_notification> … closing tag |
core/src/context/agent_message_board_notification.rs:19-25 |
skill |
<skill> … </skill> |
ext/skills/src/fragments.rs:89-91 |
user_shell_command |
<user_shell_command> … closing tag |
core/src/context/user_shell_command.rs:44 |
turn_aborted |
<turn_aborted> … closing tag |
core/src/context/turn_aborted.rs:34 |
subagent_notification |
<subagent_notification> … closing tag |
core/src/context/subagent_notification.rs:34-36 |
recommended_plugins |
<recommended_plugins> … closing tag |
core/src/context/recommended_plugins_instructions.rs:43 |
Subagent input and copied forks¶
Later role: "user" messages in a subagent's rollout stay user: the parent's v1 send_input tool
submits plain user input to the child
(core/src/agent/control.rs:134-143),
and a person can also type into a subagent thread, because the TUI's /subagents picker makes it
the active thread and composer input is sent to the active thread
(tui/src/multi_agents.rs:1-5,
tui/src/app/thread_routing.rs:475-497).
The rollout records both alike.
A copied fork has no boundary beyond the ancestor's session_meta and the
per-message inherited_user_message flag, so the other copied records stay
in the fork's file under the fork's session id.
Source host rows¶
The agent records no source-host integration in a file the collector gathers (source at
4e7b9e2,
grepped for github, gitlab, bitbucket, oauth, installation, repositor, pull request,
clone and cloud). A Codex Cloud task runs in a server-side environment bound to a GitHub
repository, which codex cloud looks up from the local repository's git origins
(wham/environments/by-repo/github/<owner>/<repo>;
cloud-tasks/src/env_detect.rs:50-63), so
the connected GitHub account, its grant, the repository list and the task conversations are
server-side only; the one local trace is the error.log of
Sync, relay and origin rows, outside every catalog path. The network
proxy's credential broker substitutes a GH_TOKEN or GITHUB_TOKEN from the environment into
requests to GitHub hosts and stores nothing
(network-proxy/src/credential_broker/providers/github.rs:13-20).
A GitHub MCP server's configuration in config.toml is an MCP server, not a Codex integration.
Confidence¶
- High: everything in Per-user storage to
Where the project path is recorded cited to source, which the local listing
matches; the file layout and naming, the envelope, the written type set and the legacy and
paginated split, the
session_meta,turn_contextandResponseItemfield names, timestamp formats and units, and thehistory.jsonlandsession_index.jsonlshapes (source, and the local tabulation agrees for every type it contains); thestate_5.sqliteandthread_history_1.sqlitecolumns anditem_jsonshapes, from the migrations and structs at 3e23877, whose table and column names agree with the Windows install. - High, observed: the desktop-only files and tables in Codex Desktop inside the ChatGPT app and the credentials they add (Windows, Codex Desktop 0.162.0-alpha.17.2); Standalone CLI on Linux (Ubuntu 26.04.1, Codex CLI 0.162.1); macOS differences (macOS 26.6.2, Codex Desktop and Codex CLI 0.162.1); the layout in Standalone CLI on Windows (Codex CLI 0.162.1); and macOS, ChatGPT 26.1013.40934 with the records of Record schema and Other stores it showed (Codex Desktop 0.163.0-alpha.10.2, one step per listing).
- Medium: that copied forks can contain an ancestor's
session_meta(inferred from the metadata sync guard, not from a file); the exact payload ofitem_completedfor item kinds not seen locally (read from the structs, not from files); theAgentPathstring form in the sample (/root/...is illustrative);cap_sidholding capability SIDs (inferred from key names and value lengths); the pairing of thestate_5.sqliteandthread_history_1.sqlitetimes with the rollout's, since the rows rest on a synthetic fixture and no real install's values were read. - Not determined: pre-envelope rollouts from the earliest releases; whether
local_shell_call_outputexists (the parser lists it butResponseItemhas no such variant at3e23877; it is harmless); how often the harness emitsagent_messageresponse items versusinter_agent_communicationlines; whattui-thread-reference-capabilities/<thread uuid>records beyond its 0-byte name; the purpose of.codex-provisioning-*.guardbeyond its name; where remote control's server token is kept; whatchatgpt_async_statusandconversation_origintppmean; whichlogs_2.sqliterows survive the desktop app's delete.