npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@standard-software/ccxlog

v1.14.0

Published

Discover and merge Claude Code and Codex CLI session logs (JSONL) into a single readable Markdown timeline.

Downloads

506

Readme

ccxlog

Language: Japanese

ccxlog is a command-line tool that combines Claude Code and Codex CLI session logs into one readable Markdown timeline.

When development of ccxlog began in 2026, code-generation AI—especially Claude Code and Codex—had become a major part of software development. Git can preserve the code produced by AI, but the development process itself—what you asked the AI to do and how it responded—is not left behind in an easily reviewable form.

ccxlog collects your project's conversations with Claude Code and Codex and records them chronologically in CCXLOG/ccxlog.md. Even when you use both tools, you can read their instructions and responses as one project history.

We hope ccxlog helps you preserve your development history and improve your productivity.

ccxlog and cclog

Its predecessor, @standard-software/cclog, is dedicated to Claude Code logs. In contrast, ccxlog supports both Claude Code and Codex CLI, merging them into ccxlog.md by default.

The main features of cclog have been incorporated into ccxlog. Because ccxlog can also run in Claude Code-only mode, migrating from cclog is easy.

Install

npm install -g @standard-software/ccxlog

The package is published on npm as @standard-software/ccxlog. The source repository is standard-software/ccxlog. The installed CLI command is just ccxlog.

Usage

Run it inside any project directory you've used with Claude Code and/or Codex:

cd /path/to/your/project
ccxlog

This writes the most recent 8 local calendar days to CCXLOG/ccxlog.md, with older Q&A pairs in CCXLOG/ccxlog_archive.md. If every pair fits in the recent window, the archive file is not created. Both files remain chronological.

To export a single source:

ccxlog -cc      # Claude Code only  -> CCXLOG/cclog.md
ccxlog -cx      # Codex only        -> CCXLOG/cxlog.md

The three aggregate families (ccxlog.md, cclog.md, cxlog.md, plus an _archive.md companion when needed) coexist in the output directory; each mode only touches its own family.

Log locations and discovery

Claude Code writes session logs under a directory that encodes the project's absolute path: ~/.claude/projects/<encoded project path>/. Codex CLI stores session logs by date under ~/.codex/sessions/.

ccxlog discovers these JSONL files and reads the sessions that belong to the target project. To read logs from another location, use claude.extraLogDirs or codex.extraLogDirs in the configuration file.

Logs written under another config directory are found as well. When Claude Code runs with CLAUDE_CONFIG_DIR (one directory per account, say ~/.claude-work), its logs go to ~/.claude-work/projects/ instead; Codex does the same with CODEX_HOME. Such a variable is usually set only while the tool runs, so ccxlog does not rely on it alone: besides the default directory and the variable (when set), it searches the home directory for entries matching .claude* / .codex* that hold projects/ / sessions/, again on every --watch cycle. Whose project a log belongs to is decided exactly as for the default directory, so logs of other projects are never mixed in. A session that sits under two of them (a copied history) is written once, keeping what only the later copy has. --verbose lists the directories found, with the account logged in to each. See autoDiscover and configDirGlobs below.

Options

ccxlog [project-path] [options]

Arguments:
  project-path           Project directory (defaults to the current directory).

Options:
  -cc                    Claude Code logs only  -> CCXLOG/cclog.md
  -cx                    Codex logs only        -> CCXLOG/cxlog.md
  --out <dir>            Output directory (default: <project-path>/CCXLOG).
  --per-session          Write one file per session (cclog_<id>.md / cxlog_<id>.md)
                         instead of the aggregated file.
  --init-template        Copy the bundled template into <out>/templates/ and
                         rewrite ccxlog.config.json to use the local copy
                         (lets you edit it without touching the global install).
  --backup-jsonl         Back up only: copy the discovered source .jsonl logs
                         into <out>/backup_jsonl/<yyyy-mm-dd_hh-mm-ss>_<hostname>/
                         and exit WITHOUT writing any Markdown (preserves the raw
                         logs locally — e.g. before swapping PCs, since the source
                         log location is derived from the machine-specific project
                         path). The folder name embeds the machine name so backups
                         stay attributable per PC.
  --backup-md            Back up only: copy the already-exported Markdown in <out>
                         into <out>/backup_CCXLOG_md/<yyyy-mm-dd_hh-mm-ss>_<host>/
                         and exit WITHOUT regenerating anything. (Automatic
                         pre-overwrite backups are stored separately, in
                         backup_CCXLOG_md_auto/.)
  --lock                 Explicitly request the exclusive output lock (write
                         operations and --watch lock automatically; this option is
                         retained for compatibility).
  --force-unlock         Remove a stale lock left by a crashed run (use with --lock).
  --stop                 Stop the --watch running for this project (its output
                         directory), from another terminal.
  --stop-all             Stop every --watch started in this environment.
  --watch                Run repeatedly: process, wait N seconds, process again
                         (N = watchIntervalSeconds in ccxlog.config.json; default 5).
  --watch=<n><unit>      Same, but stop after the given duration measured from the
                         start (positive integer + one unit s/m/h/d, e.g.
                         --watch=60s). A number without a unit (--watch=60) is an
                         error.
  --dry-run              Don't write files; report what would be written.
  --verbose              Verbose logging.
  -v, -V, --version      Show version and exit.
  -h, --help             Show this help.

Watch mode

ccxlog --watch keeps a single ccxlog run going: it processes, waits N seconds, processes again, and so on, so CCXLOG/ccxlog.md stays current while you work. It combines with -cc / -cx / --out / --per-session / --dry-run / --verbose; each cycle is exactly the same operation as one plain run, including the smart-write behavior described below (unchanged content is a noop, so the file you have open in an editor is not rewritten every 5 seconds).

ccxlog --watch            # until you stop it
ccxlog --watch=60s        # stop 60 seconds after the start
ccxlog -cc --watch=2h     # Claude Code only, for two hours
  • The wait is fixed, not a fixed period: with a 5 s wait and 3 s of processing, cycles start roughly every 8 s. Cycles never overlap or catch up.
  • The duration unit is required and single: s, m, h or d, from 1s to 366d. --watch=60, --watch=060s and --watch=1h30m are usage errors.
  • A watch holds the output lock from startup until it stops, including between cycles. Another watch or one-shot ccxlog writing to the same output directory exits with a lock error. One-shot write operations also lock automatically.
  • --watch cannot be combined with --force-unlock: a watch would tear off someone else's lock on every cycle. If a crashed run left a stale lock behind, clear it with a one-shot ccxlog --lock --force-unlock and then start the watch.
  • The wait length comes from watchIntervalSeconds in the configuration file (default 5). It is re-read every cycle, so editing it takes effect without a restart.
  • By default only cycles that changed something print a line. --verbose prints the full per-cycle detail.
  • Exit codes: 0 when the duration elapses or ccxlog --stop stops it, 130 for Ctrl+C, 143 for kill (POSIX), 1 for a startup failure, 2 for a usage error. A failed cycle does not stop the watch and does not change the exit code.

The = form is safe to type as-is in PowerShell, bash, zsh and cmd.exe; no shell quoting is required.

Incremental re-parsing

From the second cycle on, a watch only re-reads log files that actually changed. It keeps the parsed sessions of the previous cycle in memory and compares each discovered file by four attributes — size, mtime, device id and inode. If all four match, the file is neither re-read nor re-scanned; the Codex "this rollout belongs to another project" verdict is remembered the same way. Files that changed are re-read, new files are read, and files that are no longer discovered are dropped from memory.

A Claude Code log that grew is not read again from the top either. Claude Code only appends to a session log, so the watch reads just the lines added since the last cycle and keeps the rest of the analysis. This is what keeps a cycle fast while a long session's log is being written: on a 359 MB log, reading one cycle's worth of new lines takes milliseconds instead of about two seconds. The log is read from the top instead whenever it cannot be shown to be the same file, grown: a different file under the same name, a shorter file, or changed bytes at either end of the part already read. Codex rollouts are always read from the top.

  • The output is unaffected: a cycle that reuses cached parses writes exactly the same bytes as a cold run over the same logs. Merging, de-duplication, ccxlogid numbering and the smart-write decision all stay identical.
  • Only sessions that reached the output keep their parsed data. A file that was read and then discarded — not this project's, or not this source's format — is remembered by its verdict alone, so a watch holds no more in memory than a single run does.
  • The cache lives only inside the running watch process. Nothing is written to disk, and it is gone when the watch stops. The first cycle is always cold.
  • Any change to the configuration, the target project or the mode drops the whole cache, so the next cycle re-reads everything. Symlinked paths are resolved afresh every cycle, so re-pointing a symlink also takes effect on the next one — including a link a session's own cwd goes through: a remembered "belongs to another project" verdict is re-checked against that resolution each cycle and thrown away as soon as it changes.
  • --verbose reports the split for every cycle, e.g. … , cache 2 reparsed / 238 reused, and how many of the reparsed files were read only from where the last read stopped.

Reading the --verbose counters. files is what discovery found; fully read is how many of those files were analysed for this cycle — not how many were read from disk. Under --watch, a file whose parse came from the cache is still counted in fully read. The number actually read from disk this cycle is reparsed; reused is the rest. In a quiet cycle you will therefore see a large fully read next to reparsed: 0, and that is the mechanism working.

Peak memory is set by the first (cold) cycle and does not grow with the number of cycles. Reading only the appended lines costs a little more: for each Claude log written in the last 30 minutes, the watch keeps the unfinished end of the conversation (about 20 MB for the 359 MB log above), and lets it go once the log has been quiet for 30 minutes.

Quiet cycles

When nothing has changed since the last cycle, a watch does not even render. The cycle ends right after discovery when all of these are true:

  • every discovered log was reused unchanged;
  • the configuration, template and session names are the same;
  • the date has not changed;
  • the Markdown files in the output directory are exactly as the last cycle left them.

In that case the last cycle's output is still correct. Under --verbose the cycle prints No input or output changes since the last cycle; skipped rendering. and reports its files as noop.

  • Any of these changes ends the skip:
    • a log that grows, appears or is removed;
    • an edited configuration or template;
    • a renamed Codex thread;
    • an output file edited or deleted by hand;
    • midnight, when blocks move from the recent file to the archive.
  • On a real project with 504 log files and 5,169 blocks (13 MB of Markdown), a quiet cycle went from 1.6 s to 0.1 s. A cycle in which a log did change still renders everything, as before.

A cycle in which a log did change leaves the archive alone when it can. The archive holds everything older than recentDays and is usually by far the larger file, yet an ongoing conversation only ever changes the recent one. When the archive this cycle renders is exactly what the last cycle left on disk, and the file still has the size and modification time it was left with, the watch does not read, hash or compare it again; whether anything would be lost is then decided from the recent file, with the same answer as comparing both. Under --verbose such a cycle prints Archive unchanged since the last cycle: <name> not re-read. An edited, replaced or deleted archive, or blocks crossing into it, is compared in full as before. On a project with a 26 MB archive this took a changing cycle from about 1.7 s to 0.7 s.

Stopping a watch

Press Ctrl+C in the terminal that runs it. The watch finishes the cycle it is in, prints a summary, and exits with code 130.

From another terminal, use ccxlog --stop or ccxlog --stop-all:

ccxlog --stop                       # the watch for the project in the current directory
ccxlog C:\path\to\project --stop    # the watch for that project
ccxlog --out D:\logs --stop         # the watch writing to that output directory
ccxlog --stop-all                   # every watch started in this environment
  • --stop finds the watch through the lock in the output directory and asks it to stop. The watch notices within half a second of finishing its current cycle. It then prints its summary, releases the lock and exits with code 0.
    • This works the same on Windows.
    • It also reaches a watch running in another environment that writes to the same folder, such as a watch inside a devcontainer stopped from the host.
  • --stop-all stops every watch registered in this environment.
    • Each watch registers itself in ~/.ccxlog/watches/ while it runs.
    • Windows, WSL and each container have their own home folder and processes, so run --stop-all in each environment where watches run.
  • --stop also removes a lock left behind by a watch that is no longer running.
  • Watches started by a ccxlog older than 1.12.0 cannot be asked to stop.
    • --stop checks that the pid really is ccxlog in this environment, then terminates it by pid.
    • --stop-all does not see these watches, because they never registered. Restart them once with the new version.

You can also stop a watch with your OS, using the pid printed in the start banner:

ccxlog watch started (pid 12345): interval 5s, duration unlimited, mode both
  project: C:\Users\you\projects\my-app
  output:  C:\Users\you\projects\my-app\CCXLOG\ccxlog.md
Press Ctrl+C to stop, or run ccxlog --stop for this project from another terminal (or terminate pid 12345, e.g. taskkill /F /PID 12345).
  • Windows: taskkill /F /PID 12345 stops the watch immediately. It leaves the lock behind. The next ccxlog run, watch or --stop in that folder sees that its holder is gone and removes it automatically.
  • Linux / WSL / macOS: kill 12345 — this is a graceful stop (SIGTERM): the current cycle finishes, the summary is printed, and the exit code is 143.

The output file is committed atomically (write to a temporary file, then rename), so stopping a watch at any moment — including a force kill — never leaves a half-written Markdown file behind.

Manual and automatic output Markdown backups

The automatic backup is a last line of defense against losing conversation pairs. When ccxlog must rewrite existing output Markdown, it backs up the file first only if at least one ccxlogid present in the old file would be missing from the new content — for example when an older Q&A disappears because its source JSONL has expired. When the old file cannot be judged safely (no valid ccxlogid, malformed or duplicate ids, or the new content fails to parse), ccxlog also backs up, erring on the safe side.

No backup is created for a first-time file, unchanged content, a strict append, or a rewrite that keeps every existing ccxlogid (answer updates, template changes, inserting a Q&A at an earlier point in the timeline, reordering, and so on).

Automatic backups are stored in their own folder, separate from manual --backup-md copies — anything appearing here is a signal that previously rendered pairs were about to disappear:

CCXLOG/backup_CCXLOG_md_auto/<yyyy-mm-dd_hh-mm-ss>_<hostname>/

The backup is created and verified before the rewrite. If verification fails, the original Markdown is not modified. This lets you recover content that had already been exported to ccxlog.md even after it disappears from the source JSONL logs.

Beside the copies, each folder holds a ccxlog-backup-reason.txt naming what was about to be lost — the file, the block count before and after, and every missing ccxlogid with the first line of its block (the heading, so the date, source and session are readable at a glance) — plus the ccxlog version that made the rewrite. A run that backs up several files appends one section per file. This is what tells you whether a backup means a source log has expired (the blocks now exist only in this folder, and the sessions named in the record are the ones to check) or simply that an upgrade changed how blocks are formed.

You can also create a backup manually at any time:

ccxlog --backup-md

--backup-md is a standalone backup action. It copies exported Markdown managed by ccxlog and exits without rediscovering logs or regenerating Markdown.

Backing up the raw JSONL logs

The source logs both tools write live in folders whose names/locations are derived from the machine (Claude Code encodes the project's absolute path; Codex groups sessions by date). Move to a different machine (or a different path) and ccxlog may no longer see the old sessions. To keep a local copy of the raw logs before that happens:

ccxlog --backup-jsonl

The destination has this structure:

CCXLOG/
└─ backup_jsonl/
   └─ <yyyy-mm-dd_hh-mm-ss>_<hostname>/
      ├─ cc/   ← Claude Code JSONL, mirroring the live layout:
      │        <session id>.jsonl and <session id>/subagents/agent-*.jsonl
      │        (subagent transcripts are always backed up, regardless of
      │        the includeSubagents setting)
      └─ cx/   ← Codex JSONL, preserving the <yyyy>/<mm>/<dd>/ date tree

Because the structure mirrors the live log layout, a snapshot can be read back exactly like the real log folders:

"claude": { "extraLogDirs": ["backup_jsonl/<stamp>/cc"] },
"codex":  { "extraLogDirs": ["backup_jsonl/<stamp>/cx"] }

--backup-jsonl is a standalone action: it backs up only and exits without (re)generating Markdown. Combine it with --dry-run to preview the destination or --verbose to see each copied file.

Files already under the destination (<out>/backup_jsonl/) are never used as a copy source, so repeated backups do not snowball into backups of backups. This makes it safe to point extraLogDirs at earlier snapshots (e.g. backup_jsonl/<stamp>/cc) to keep rendering sessions whose original logs have expired — the cross-file de-duplication collapses the overlap with live logs.

Configuration

Drop a ccxlog.config.json in the output directory (<project>/CCXLOG/ccxlog.config.json) to customize behavior. Settings shared by both sources sit at the top level; source-specific settings live under a claude or codex namespace:

{
  "extraCwds": [
    "C:\\Users\\you\\projects\\another-project",
    "/home/you/projects/another-project"
  ],
  "includeSubdirectories": true,
  "autoDiscover": true,
  "watchIntervalSeconds": 5,
  "recentDays": 8,
  "autoBackupGraceMinutes": 10,
  "template": "templates/english.md",

  "claude": {
    "extraLogDirs": [],
    "configDirGlobs": [".claude*"],
    "includeSubagents": true
  },
  "codex": {
    "extraLogDirs": [],
    "configDirGlobs": [".codex*"],
    "includeDeveloperMessages": false,
    "includeSubagents": true
  }
}

Use backslash-escaped paths on Windows (C:\\Users\\...) and forward-slash paths on Ubuntu/macOS (/home/you/...).

Top-level (both sources)

| Field | Description | |---------------------------|-----------------------------------------------------------------------------| | extraCwds | Additional project directories whose logs (from either tool) should be merged into the output. | | includeSubdirectories | If true (default), also collect logs from projects whose cwd is a subdirectory of the project ccxlog runs in (e.g. running in ~/work/app also gathers ~/work/app/frontend). Nested candidates are verified against each session's real cwd, so same-prefix siblings like ~/work/app-backup are never included. Set false to match only the exact project path (plus extraCwds / extraLogDirs). | | autoDiscover | If true (default), also read logs under every directory in your home directory that matches configDirGlobs and holds logs (see Log locations and discovery). Set false to read only the default directory and CLAUDE_CONFIG_DIR / CODEX_HOME when set. | | watchIntervalSeconds | How long --watch waits between cycles, in seconds. Integer, 1-86400, default 5. An out-of-range or non-integer value warns once and falls back to 5. Ignored when --watch is not used. | | recentDays | Number of local calendar days kept in the everyday aggregate. Integer of 1 or greater, default 8 (today plus the same weekday from the previous week). Older blocks go to the matching _archive.md; no archive is kept when it would be empty. | | autoBackupGraceMinutes | Recent-ID grace period for automatic Markdown backups, in minutes. Integer from 1 through 60, default 10. A disappearing block whose timestamp is inside this window does not trigger an automatic backup; this absorbs ID changes while a live response is still growing. Manual backups are unchanged. | | template | Path to a Markdown template. Resolved against ccxlog's own templates/ dir first, then your CCXLOG dir. |

The generated ccxlogid HTML-comment marker carries the pair time as Unix milliseconds (time:<number>), so this decision does not depend on the selected template or timezone. Older markers without time remain readable.

Per-source (claude / codex)

| Field | Description | |---------------------------|-----------------------------------------------------------------------------| | extraLogDirs | Additional raw log directories to read verbatim (backup snapshots, log trees copied from another machine, ...). Entries are read without the cwd filter, and may point anywhere — including under <out> (e.g. backup_jsonl/<stamp>/cc). Each source ingests only files in its own format (claude skips Codex rollouts and vice versa; unrelated .jsonl is skipped by both, reported under --verbose). | | configDirGlobs | Names of config directories to look for directly in your home directory, with * and ? as wildcards. Default [".claude*"] for claude and [".codex*"] for codex. A match is used only when it holds projects/ (claude) or sessions/ (codex). An empty array turns the search off for that source. | | includeSubagents | If true (default), render the conversations of subagents — the child AIs or threads a session delegates work to. Set false to keep only the main conversation. Each key controls its own source. See Subagents below. | | includeSidechain | (claude only) The supported former name of claude.includeSubagents; sidechain is Claude Code's historical term for the same records. It still works on its own. Setting both to different values is a fatal config error — ccxlog will not silently pick one. | | includeDeveloperMessages| (codex only) If true, include Codex developer/system messages in the output. |

Log-directory recursion is selected automatically for each source and is not configurable: Claude Code roots are scanned at the top level, while Codex roots are scanned recursively because Codex stores sessions in date subdirectories.

Subagents

Both tools can delegate work to a subagent, and both record what the subagent did. claude.includeSubagents and codex.includeSubagents decide whether those conversations are rendered; they default to true, so the output is a complete record of the session.

What counts as a subagent:

  • Claude Code — pairs marked isSidechain inside a session log, plus the separate <session id>/subagents/*.jsonl transcripts newer versions write, which are rendered as additional sessions. (sidechain is the historical name for the same thing; it survives only in the compatibility key above.)
  • Codex — a rollout whose session_meta says thread_source: "subagent" or carries source.subagent. An ordinary fork or resume is not a subagent and is never affected by this setting, even though it also carries forked_from_id.

Three things the setting does not do:

  • It does not stop the raw logs from being preserved. --backup-jsonl always copies subagent logs from both sources. Whether to display a conversation and whether to keep its log are separate questions.
  • It does not remove mentions of subagents from the main conversation. Only the child's own conversation is affected.
  • It does not make a run cheaper. Every log is still discovered and fully parsed with false. Codex subagent rollouts re-record their parent's conversation, and answers, progress and token counts that only the child recorded are merged back into the parent before anything is hidden — which can only be done by reading all of it. So false reduces what you read, not what ccxlog reads: file count, parse volume, peak memory and --watch memory are the same as with true.

Switching the setting is safe in both directions. Turning subagents on only adds blocks, so existing ccxlogids and their content stay exactly as they were and no automatic backup is needed. Turning them off removes blocks, so the previous Markdown is backed up to backup_CCXLOG_md_auto/ before the first destructive rewrite, and per-session files that belong to a subagent session are backed up and then removed rather than left behind as stale output. Re-running with the same setting and the same logs is a complete no-op.

If a project's only logs are subagent transcripts, false legitimately leaves nothing to show: ccxlog writes a 0-pair file and exits 0. That is different from finding no logs at all, which is still an error that leaves existing output untouched.

Templates

Six templates ship out of the box:

  • templates/english.md (default)
  • templates/japanese.md
  • templates/english-with-progress.md
  • templates/japanese-with-progress.md
  • templates/english-with-progress-full.md
  • templates/japanese-with-progress-full.md

A template can use the following placeholders:

| Placeholder | Replaced with | |-------------------|----------------------------------------------------------| | %DateTime% | Question timestamp (YYYY/MM/DD Day HH:MM:SS) | | %Source% | The tool that produced the pair — ClaudeCode or Codex | | %SourceShort% | Short source tag — cc or cx | | %CcxlogId% | ccxlogid — an answer-independent, cross-tool-stable id used as the block's identity marker | | %SessionId% | The session id | | %SessionName% | Human-readable session name — for Codex, thread_name from ~/.codex/session_index.jsonl; for Claude Code, the custom or generated title; empty when unavailable | | %Account% | The account logged in to the config directory the log was found under (oauthAccount.emailAddress in Claude Code's .claude.json, the email in Codex's auth.json). Neither tool records the account in the log, so this is the login there now; empty when unknown or for extraLogDirs logs. Not in the built-in templates; e.g. [%Source%@%Account%] | | %Question% | The user's message | | %Answer% | The assistant's reply | | %Progress% | (optional) Tool calls between Q and A, summarized | | %ProgressFull% | (optional) Same, but full tool input/output JSON + thinking | | %Model% | Model that produced the answer; synthetic entries are skipped | | %Version% | Tool version the pair ran under | | %GitBranch% | Git branch at the time of the question | | %Cwd% | Working directory at the time of the question | | %Tokens% | Token usage summed over the pair's assistant turns. Out-of-source fields stay blank, so a real 0 is distinguishable from "the tool never reported it". |

Whether — and how verbosely — the progress section is rendered is decided entirely by the template:

  • contains neither → tool calls are omitted;
  • contains %Progress% → summarized (tool name + key arg, result head only);
  • contains %ProgressFull% → full input/output JSON and thinking blocks.

Use one of the two progress placeholders, not both. (There is no CLI flag for this — verbosity follows the template.)

A template that references neither placeholder also costs nothing in memory: the raw tool input/output between Q and A is dropped as soon as a session is parsed, instead of being kept around (which matters most for --watch, whose resident set holds the previous cycle's analysis). Editing the template to add or remove a progress placeholder is picked up on the next cycle — the analysis cache is dropped and every file is re-read, so the output is always the one the current template asks for.

Customizing a template

To edit a template without touching the globally-installed ccxlog files, run:

ccxlog --init-template

This copies the template currently set in ccxlog.config.json (or the English default if no config exists) into CCXLOG/templates/ and rewrites the config to point at the local copy. After that, edit the copied file directly. Re-running --init-template when the destination already exists prints an error and does not overwrite, but still re-applies the config rewrite.

The formal identity marker is the exact standalone line <!-- %CcxlogId% -->, rendered as <!-- ccxlogid:<24 hex digits> -->. You may place that line anywhere in a custom template. If it is absent — even when %CcxlogId% appears inline elsewhere for display — ccxlog automatically prepends the formal marker to every Q&A block. %DateTime% is human-readable metadata, not a block identity.

Output format

ccxlog.md is a flat chronological sequence of Q&A blocks. Each block is rendered from the template. By default (English template):

<!-- ccxlogid:1f3c... -->
# 2026/05/27 Wed 11:03:49   [ClaudeCode] Session:My first session:ec5e9974-...
Model=claude-opus-4-8 Version=2.1.205
Branch=main Cwd=C:\Users\satoshi\projects\my-app
Tokens=in 6, out 33, cache read 21,758, cache write 8,730
## Question
Hello, can you help me with X?

<!--
## Answer
Sure, here's how...
-->

----------------------------------------

The [ClaudeCode] / [Codex] tag comes from %Source%, so a merged timeline makes clear which tool each turn belongs to. The <!-- --> around the answer keeps the assistant's own Markdown (headings, lists, code) from colliding with the template's structure, and as a side effect Markdown viewers collapse it so long replies don't dominate the preview. Remove it from your template if you'd rather see answers expanded by default.

File update behavior

ccxlog modifies an output file only when the generated result has changed. When new Q&A content belongs strictly at the end of the existing timeline, it appends only the new content instead of rewriting the entire file.

As a result, running ccxlog while ccxlog.md is open in an editor such as Visual Studio Code does not force a full-file reload that returns the view to the top. You can keep your reading position while the latest logs are appended. When nothing has changed, the file's modification time is preserved as well.

Notes

  • Chronological merge. Pairs from both tools are sorted by a stable 8-key comparator (question timestamp first, then source, session, and id tiebreakers), so the same set of logs always renders in the same order regardless of read order.
  • Cross-session de-duplication. When a Claude Code session is resumed or forked, the earlier history is copied verbatim into the new session file, so the same turn would otherwise appear once per file. In the aggregate output a pair is dropped when any of its message uuids (question, steering follow-ups, or answer) was already emitted — matching by uuid, so it is lossless. Codex session ids are per-file positional, so Codex pairs are never merged this way. --per-session output is intentionally left un-deduplicated so each session file stays a complete transcript.
  • Codex subagents. Every time Codex CLI starts a subagent it writes a new rollout and copies the parent session's whole conversation into it, with each copied line's timestamp rewritten to the child's start time — so old questions used to reappear as new ones. A copied pair is dropped only when the identical pair is confirmed present in an older rollout of the same lineage; a pair the ancestor never had (a compacted child holds history the parent lost) is kept, and an answer only the copy had is merged into the surviving original. The instructions a subagent receives are rendered as questions of their own, and a subagent is listed under its own thread id and agent nickname. Set codex.includeSubagents: false to hide the child conversations entirely; the matching above still runs first, so nothing the child alone recorded is lost.
  • Cancelled questions are kept. If a Claude Code turn is interrupted before any assistant output and the message is retyped, the cancelled question is still emitted as its own pair with an empty Answer — including chains of consecutive cancellations. Follow-up messages typed while a turn has produced no answer yet keep being merged into the same pair as before. (Since 1.4.0; previously the retyped message silently replaced the cancelled question.)
  • Harness notifications are skipped. Claude Code injects a <task-notification> block as a user-role message when a background task finishes. A pair whose question is only such blocks and whose answer is empty contains no words from either side of the conversation, so the aggregate output skips it. A notification accompanied by typed text, or one whose turn produced an answer, is kept, and --per-session files keep every pair.
  • Output content is rebuilt from the source logs on every run. If you delete a source log, the corresponding pairs disappear on the next run. The actual file update is classified as a no-op, strict append, or full rewrite.
  • Pre-overwrite backup of the Markdown. Whenever a run must rewrite an existing output .md in a way that would drop at least one ccxlogid the file already contains (or the comparison cannot be made safely), the existing file is first copied to CCXLOG/backup_CCXLOG_md_auto/<yyyy-mm-dd_hh-mm-ss>_<hostname>/ so the previous version is never lost. Backup folders accumulate and are never pruned. A first-time create, an unchanged run, a strict append, or a rewrite that keeps every existing ccxlogid (template changes, insertion of an earlier Q&A block, answer updates) produces no backup. When a backup is required it is taken and verified before the rewrite; if it cannot be verified, the rewrite is aborted. Each backup folder also gets a ccxlog-backup-reason.txt recording what was about to be lost.

License

MIT