Skip to main content

cc-warehouse

PyPI Python License

Turn your Claude Code sessions into a readable, permanent archive.

cc-warehouse captures every session as it ends, stores the original JSONL untouched, and renders it into markdown and HTML you can actually read, search and share. The result is a plain folder tree: no database to keep alive, no service to run, nothing that stops working when the tool does.

ccw doctor        # is capture working, and if not, since when?
ccw archive --to ~/my-archive

Why

Claude Code writes each session to ~/.claude/projects/<project>/<uuid>.jsonl and does not promise to keep it. The file is newline-delimited JSON built for a program to replay, not for a person to read: tool calls, thinking blocks, system reminders and prose all interleaved.

cc-warehouse solves both halves of that:

  • Durability. Sessions are copied out on capture and never modified or deleted afterwards. Every write is atomic (temp file plus os.replace), so an interrupted run cannot leave a half-written file.
  • Readability. Each session becomes four rendered files beside its raw JSONL, in a folder named so it sorts chronologically and means the same thing on any machine.

Install

uv tool install cc-warehouse

or with pipx:

pipx install cc-warehouse

Requires Python 3.12+. The runtime uses only the standard library: no third-party packages are pulled in, at install time or at run time.

Both ccw and cc-warehouse are installed as entry points; ccw is used throughout this document.

Linux and WSL2

Nothing extra is required. If uv is not installed yet:

curl -LsSf https://astral.sh/uv/install.sh | sh
source "$HOME/.local/bin/env"
uv tool install cc-warehouse

You do not need to install Python first. uv fetches its own interpreter, so a distribution shipping an older Python (Ubuntu 22.04 ships 3.10) works as well as a current one.

Two things specific to WSL2:

  • Keep the warehouse on the Linux filesystem, under $HOME, not under /mnt/c. Locking uses os.link and every write is a temp file plus fsync, chmod and os.replace. Those are ext4-native; their behaviour across the Windows filesystem bridge has not been measured here, and a warehouse is the wrong place to find out.

  • Claude Code running on Windows writes somewhere else. Its sessions live at C:\Users\<you>\.claude\projects, which WSL sees as /mnt/c/.... ccw sweep looks at ~/.claude/projects by default, so pull the Windows side in explicitly, read-only:

    ccw sweep --dry-run --source /mnt/c/Users/<you>/.claude/projects
    

There is no launchd on Linux, so the periodic sweep that catches sessions a SessionEnd hook cannot see (a killed process) belongs in cron or a systemd user timer:

crontab -e
# 0 * * * * $HOME/.local/bin/ccw sweep --quiet

Use the absolute path. A cron job does not load your shell profile, so a bare ccw will not be found.

If uv refuses to install this release

Some setups configure uv with a release-age cutoff (exclude-newer in ~/.config/uv/uv.toml, [tool.uv], or the UV_EXCLUDE_NEWER variable) so that freshly published packages are not installed immediately. If yours is older than this release, uv will report that cc-warehouse was filtered by exclude-newer.

That is your configuration working, not a broken package. Move your cutoff forward, or override it for the one command:

UV_EXCLUDE_NEWER=2999-01-01T00:00:00Z uv tool install cc-warehouse

The environment variable outranks both config files, so this works whichever of them holds your cutoff.

Quick start

Import everything Claude Code has written so far, then build the archive:

ccw sweep --dry-run          # show what would be imported; writes nothing
ccw sweep                    # import it
ccw archive --to ~/my-archive
ccw archive --to ~/my-archive --verify

Every command that writes accepts --dry-run, and it is enforced centrally rather than per-command, so a dry run cannot quietly modify anything.

To capture new sessions automatically, register ccw hook as a SessionEnd hook in ~/.claude/settings.json:

{
  "hooks": {
    "SessionEnd": [
      { "hooks": [ { "type": "command", "command": "ccw hook", "timeout": 45 } ] }
    ]
  }
}

Then confirm it is actually running:

ccw doctor

ccw doctor exists because a hook that never fires produces no error, and silence and idleness look identical. It reports whether a capture hook is registered, when the last capture happened, and how the tool is installed.

What you get

my-archive/
└── myuser-my-project/
    └── 20260608-171250+1000_e27cb3f8-99fe-4144-98eb-04bcac53c956/
        ├── e27cb3f8-99fe-4144-98eb-04bcac53c956.jsonl   original, byte for byte
        ├── transcript.md                                 full conversation
        ├── transcript.compact.md                         prose only
        ├── conversation.html                             full, self-contained
        ├── conversation.compact.html                     prose only, self-contained
        └── manifest.json                                 what produced these files

The folder name is <YYYYMMDD-HHMMSS><offset>_<session-uuid>. The UTC instant is converted using a timezone pinned in config rather than read from the machine clock, so the same session produces the same folder name anywhere. The offset is part of the name because zones with daylight saving make a bare local timestamp ambiguous once the tool that wrote it is gone.

manifest.json records config, counts, source_hash, subagents, and three separate accounting keys that are deliberately not merged:

Key Means
loss content the renderer dropped
unrecognised entry types this parser does not name yet
withheld content that never arrived from Claude Code

Keeping them apart matters: an entry that rendered as a marker is not a lost one, and a new Claude Code entry type increments unrecognised instead of vanishing silently.

Commands

Command Purpose
ccw hook capture a session from a SessionEnd payload on stdin
ccw sweep import transcripts the hook missed (--source DIR)
ccw archive build, or --verify, the archive tree at --to DIR
ccw render rebuild one session's files, or render an ad-hoc transcript
ccw build rebuild projections from the catalog
ccw share build a sanitized static site for chosen sessions
ccw status recent captures, counts, store size, last errors
ccw doctor is capture working, and if not, since when
ccw verify re-hash stored objects and cross-check the catalog
ccw reindex rebuild the catalog from the archive tree alone
ccw project list / show / rename / move / merge projects
ccw import adopt a foreign transcript tree (--from DIR)
ccw migrate one-shot import of a legacy archive
ccw relocate repair paths after a project directory moves
ccw version print the version

Run ccw <verb> -h for a command's options.

Sharing

ccw share builds a static site for chosen sessions, scrubbing secret-shaped content first. API keys, tokens and private key blocks are detected and replaced before anything is written.

ccw share s:a1b2c3d4 --out ./public-site

Shared pages inline their own syntax highlighting and make no third-party requests, so opening one does not tell anyone else that you did. Publishing unscrubbed content is possible but requires an explicit flag whose name is meant to be uncomfortable to type.

See docs/sharing-and-redaction.md for what is detected and what is not.

Configuration

Configuration is TOML, read from two locations, lowest precedence first:

  1. ~/.config/cc-warehouse/config.toml (or $XDG_CONFIG_HOME)
  2. <data-root>/config.toml

Per-project overrides go in a [project.<id>] section. Environment variables (CCW_ROOT, CCW_SKIP_HOOK, CCW_VOICE_URL, CCW_VOICE_ID, CCW_OPEN_FOLDER, CCW_WEBHOOKS) override files, and command-line flags override everything.

The data root defaults to ~/cc-warehouse-data and can be moved with CCW_ROOT or the root key.

An invalid value is never silently replaced by a default: it is recorded as a config error and the default is kept, so a typo is visible rather than merely survivable.

Design notes

A few properties are structural rather than incidental:

  • Sessions are never deleted or modified. Sources and stored objects are read-only to this tool.
  • Every write is atomic, so a crash or a full disk cannot corrupt a file that was already good.
  • Batch operations name what failed and continue rather than aborting on the first bad item, so one malformed session cannot hide the other 10,000.
  • Identity is content, not path. Moving or renaming a project directory does not create duplicates or orphans.

Development

git clone https://github.com/CaptainCodeAU/cc-warehouse
cd cc-warehouse
uv sync

uv run pytest          # oracle suite
uv run pyright         # strict mode
uv run ruff check

pyright in strict mode and ruff are merge gates. Tests may import pytest; nothing else third-party is permitted anywhere in the project.

New to uv? curl -LsSf https://astral.sh/uv/install.sh | sh and nothing else is needed. Installing cc-warehouse from PyPI requires no special setup at all and is unaffected by everything in the next paragraph, which applies only if you clone this repository.

The pinned resolution cutoff

pyproject.toml sets [tool.uv] exclude-newer to a fixed date. uv refuses to resolve any package published after it, and records the cutoff in uv.lock, so a clone resolves the same dependency versions today as it did months ago rather than whatever is newest.

Two consequences worth knowing before it surprises you:

  • uv add <package> resolves against the index as of that date, so a recent release will look missing. That is the pin working. Move the date in the same commit, and run uv lock so the recorded cutoff matches.
  • An exported UV_EXCLUDE_NEWER overrides the pin, because the environment outranks project configuration in uv. If your shell sets one (some supply-chain-hygiene setups export a rolling value), your uv.lock will show a different cutoff than the pin and appear permanently modified. Unset it for this repository, or leave the lock alone.

The full precedence, measured on uv 0.12.1 rather than assumed, is recorded in the comment above the setting in pyproject.toml.

Releasing

RELEASING.md carries the checklist, the versioning rules, and the one-time Trusted Publishing setup. The rule most easily missed: a change to the default rendered output is a breaking change, because a user's archive is something they read and link to. tests/golden/matrix-anchor enforces it mechanically.

Status

Capture, rendering, the archive tree, sharing, and the integrity and diagnostic commands are implemented and in daily use. Full-text search (ccw search) and an MCP server (ccw mcp) are planned.

Changes are recorded in CHANGELOG.md.

License

PolyForm Noncommercial 1.0.0. Free for noncommercial use: personal projects, research, and evaluation. Commercial use requires a separate license. This is a source-available license, not an OSI-approved open source one; please read it before depending on this in a business context.

Release files for cc-warehouse 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cc-warehouse 0.1.2
File Size Uploaded
cc_warehouse-0.1.2.tar.gz 815.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for cc-warehouse 0.1.2
File Interpreter ABI Platform
cc_warehouse-0.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 1.1 MB

Release files / cc_warehouse-0.1.2.tar.gz

Download URL cc_warehouse-0.1.2.tar.gz
Size 815.0 kB
Tags Source
SHA-256 checksum
How to use checksums
0bf709a37f3c16a89dc2c38027c7cef32d5361e3a00da4b573f03d82580a7830
BLAKE2b-256 checksum
How to use checksums
56e50e85f32ccfbcede21b9445e8ef660bc6e860601560f801c4a6153eec7e8e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 6, 2026.

Transparency log

Release files / cc_warehouse-0.1.2-py3-none-any.whl

Download URL cc_warehouse-0.1.2-py3-none-any.whl
Size 250.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6d6d90508290fb4dbd9cd3012b7577039c0fd8f8e19980263e8574688692a745
BLAKE2b-256 checksum
How to use checksums
befdea7f69b2e6795d86ef374d16fca5dd0d467d24c1a10fa7a8dc209926dc39
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 6, 2026.

Transparency log

Release history Release notifications | RSS feed

0.1.4

2 release files

0.1.3

2 release files

This release

0.1.2 This release

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page