GUI-fidelity Markdown exports of local Codex app/CLI thread/session JSONL — full threads, turns, single responses, ranges — with frontmatter, an export map, per-turn metadata, redaction, verified Win32 Unicode clipboard, and a one-command Codex skill for $-invocation in the Codex composer.
Why this exists. Copying responses from Codex into another tool drops Unicode (I'll → IÆll), bloats your output with Chunk ID / Wall time / Process exited transport headers, hides edited-file cards behind raw patch noise, and never gives you a clean full-thread Markdown with usable metadata. This tool fixes all of that, and stays dependency-light: zero required Python packages.
git clone https://github.com/lowestprime/codex-exporter.git
cd codex-exporter
./bash/install.sh --skill # installs script + helpers + Codex skill
exec $SHELL # reload shell to pick up cdx-* helpers
cdx-sessions # list local sessions
cdx-thread <SESSION_ID> # full export to ~/codex-thread-exports/git clone https://github.com/lowestprime/codex-exporter.git
cd codex-exporter
.\Install-CodexThreadExporter.ps1 -AppendProfile -InstallSkill
. $PROFILE
cdx-thread <SESSION_ID>python Export-CodexThread.py --list-sessions
python Export-CodexThread.py --latest-session --mode thread
python Export-CodexThread.py --session-id <UUID> --mode last-response --wrap-md --clipboardThe exporter works with zero required packages. If tiktoken is installed, token counts in filenames, frontmatter, the thread map, and per-turn metadata automatically use OpenAI's cl100k_base tokenizer instead of the built-in regex estimate. No exporter flag is required.
Install tiktoken into the same Python interpreter that runs Export-CodexThread.py.
$ErrorActionPreference='Stop'
$py=(Get-Command python).Source
uv pip install --python $py --system --upgrade --only-binary=:all: "tiktoken>=0.12,<1"If you do not use uv:
python -m pip install --upgrade --only-binary=:all: "tiktoken>=0.12,<1"python3 -m pip install --upgrade --only-binary=:all: 'tiktoken>=0.12,<1'$ErrorActionPreference='Stop'
$py=(Get-Command python).Source
@'
import importlib.metadata as md
import sys
import tiktoken
text = "Codex exporter validation: I'll, I’ll, em dash —, emoji 🚀, 䏿–‡, code: `Get-ChildItem`"
enc = tiktoken.get_encoding("cl100k_base")
tokens = enc.encode(text)
print(f"python={sys.executable}")
print(f"tiktoken={md.version('tiktoken')}")
print(f"encoding={enc.name}")
print(f"tokens={len(tokens)}")
print(f"roundtrip={enc.decode(tokens) == text}")
'@ | & $py -Expected pass conditions:
encoding=cl100k_base
roundtrip=True
$ErrorActionPreference='Stop'
$py=(Get-Command python).Source
@'
from pathlib import Path
import tiktoken
script = Path("Export-CodexThread.py")
src = script.read_text(encoding="utf-8")
enc = tiktoken.get_encoding("cl100k_base")
print(f"script={script.resolve()}")
print(f"encoding={enc.name}")
print(f"roundtrip={enc.decode(enc.encode('I’ll — 🚀 䏿–‡')) == 'I’ll — 🚀 䏿–‡'}")
print(f"source_mentions_tiktoken={'tiktoken' in src}")
print(f"source_mentions_cl100k_base={'cl100k_base' in src}")
'@ | & $py -Expected pass conditions:
encoding=cl100k_base
roundtrip=True
source_mentions_tiktoken=True
source_mentions_cl100k_base=True
After this passes, run the exporter normally:
cdx-thread <SESSION_ID>or:
python Export-CodexThread.py --session-id <UUID> --mode threadThe generated token counts will use cl100k_base automatically.
In a Codex app/CLI thread composer, type /status. Copy the Session UUID. Or skip this step entirely and use --list-sessions / --latest-session.
For --mode thread, the Markdown contains:
- YAML frontmatter (title, session id, source SHA256, models detected, prompt/response counts, action counts, edited/created/deleted file counts, total lines, approximate tokens, exported timestamp)
- Top-of-file thread export map with a per-turn index table
- Per-turn prompt and response metadata (timestamps, line counts, token counts, action counts, file-card counts)
- Assistant messages, command blocks, command output blocks,
Successmarkers - Reconstructed
Edited file/Created file/Deleted filecards parsed from*** Begin Patch...*** End Patchpayloads — not rawapply_patchnoise - ANSI/OSC stripping, base64/data-URI/hex blob summarization, repeated-line compaction
- Optional secret redaction (
--redact)
Filenames are stamped:
codex_<thread_name>_WED_04292026_060940_PM-PDT_12345lines_67890tokens.md
The timezone follows your system local tz, or CODEX_EXPORT_TZ=America/Los_Angeles if set.
| Mode | What it does |
|---|---|
thread |
Full thread/chat with frontmatter, map, prompts, responses, actions |
last-response |
Latest response after the most recent user prompt |
last-substantial |
Latest assistant message ≥ --min-chars (default 1000) |
response |
Selected response block by --response N |
turn |
Selected prompt+response block by --response N, with metadata |
message |
Selected user/assistant message by --message N |
range |
Message range by --from-message N --to-message M |
chat |
User/assistant messages only, no actions |
chat-actions |
Everything: messages + actions |
actions |
Extracted actions only |
Use --list-responses to see indices for response / turn mode, or --list for indices for message mode.
| Command | What it does |
|---|---|
cdx-sessions |
List all local Codex session JSONLs |
cdx-thread <UUID> |
Full thread export |
cdx-thread-clip <UUID> |
Full thread export + clipboard copy |
cdx-response <UUID> |
Latest response → wrapped Markdown + verified clipboard |
cdx-response <UUID> -NoFile |
Clipboard only, skip writing a file |
cdx-responses <UUID> |
List prompt/response blocks |
cdx-response-block <UUID> N |
Export response block N |
cdx-turn <UUID> N |
Export prompt+response turn N with metadata |
cdx-final <UUID> |
Last substantial assistant answer |
cdx-msg <UUID> N |
Export single message N |
cdx-range <UUID> A B |
Export message range A..B |
cdx-open |
Open the export folder |
cdx-verify-latest |
Health-check the most recent export (clipboard match, no mojibake, no transport noise) |
Same names, same args. cdx-sessions, cdx-thread, cdx-thread-clip, cdx-response, cdx-responses, cdx-response-block, cdx-turn, cdx-final, cdx-msg, cdx-range, cdx-open, cdx-latest.
A first-class Codex Agent Skill is included so you can invoke the exporter from the Codex composer:
$codex-thread-export export this full thread
$codex-thread-export copy the latest response as fenced Markdown
$codex-thread-export list local sessions
The skill is self-contained — its bundled scripts/Export-CodexThread.py mirrors the canonical root script, so the skill folder works on its own.
PowerShell:
.\Install-CodexThreadExporter.ps1 -InstallSkillBash:
./bash/install.sh --skillOr copy manually:
cp -R skills/codex-thread-export "$HOME/.agents/skills/codex-thread-export"Restart Codex if the skill doesn't appear in /skills.
After running an export, cdx-verify-latest should report:
Clipboard_MatchesFile True
File_Has_Mojibake False
Clipboard_Has_Mojibake False
File_Has_ChunkMetadata False
Clipboard_Has_ChunkMetadata False
File_Has_ActionNoise False
File_Has_EditedFile True
Clipboard_Has_EditedFile True
These are the regression boundaries the tool was iterated against. If any of these flip, file an issue with the failing fixture.
Codex transcripts can contain prompts, command output, file paths, branch names, .env filenames, API keys, and private repo names. Always inspect before sharing.
For public sharing:
python Export-CodexThread.py --session-id <UUID> --mode thread --redact--redact scrubs:
- OpenAI API keys (
sk-...) - GitHub PATs and tokens (
github_pat_...,ghp_..., etc.) - Bearer tokens
- Generic
password=/token=/secret=/api_key=lines - Cloudflare tunnel tokens
The exporter strips embedded base64 images, data URIs, and oversize hex blobs by default. Pass --keep-hogs to disable (forensic local-only).
Add this to .gitignore in projects that use the tool:
codex-thread-exports/
*.raw.jsonl
*.private.*.md
| Variable | Purpose |
|---|---|
CODEX_THREAD_EXPORTER |
Override the path the bash/zsh and skill resolvers use to find the script |
CODEX_THREAD_EXPORT_DIR |
Override the default --out-dir |
CODEX_EXPORT_TZ |
IANA timezone (e.g. America/Los_Angeles) used in filename stamps |
CODEX_HOME |
Override ~/.codex for session discovery |
- The exporter reads local JSONL session files; it does not call any API or upload anything.
- Token counts are approximate unless
tiktokenis installed in the Python interpreter that runs the exporter; when available, the exporter usescl100k_baseautomatically. - Model detection is best-effort and depends on whether the local session JSONL records the model name.
- Verified Win32 Unicode clipboard is Windows-only. Other platforms use
pbcopy/wl-copy/xclip/xsel.
Export-CodexThread.py canonical Python script
Install-CodexThreadExporter.ps1 Windows installer
bash/
codex-thread-export.sh POSIX helper functions
install.sh POSIX installer
powershell/
CodexThreadExport-profile-block.v7.ps1 profile block to source
skills/
codex-thread-export/ Codex Agent Skill
SKILL.md
scripts/Export-CodexThread.py bundled copy (kept in sync via tools/sync_skill_script.py)
agents/openai.yaml
tests/
test_smoke.py 7 cross-platform smoke tests
fixtures/sample-session.jsonl synthetic Codex session for tests
tools/
sync_skill_script.py keeps skill copy in lockstep with root
.github/workflows/ci.yml CI on Linux/macOS/Windows × py3.10/3.13
See CONTRIBUTING.md. The bar for behavioral changes to the extraction core is high — please attach a real failing case before changing classify_record, extract_command, parse_apply_patch, the secret patterns, strip_hogs, or the Win32 clipboard implementation.
MIT © 2026 Cooper Beaman