cc-memory

mcp
Security Audit
Warn
Health Warn
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 5 GitHub stars
Code Pass
  • Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Pass
  • Permissions — No dangerous permissions requested

No AI report is available for this listing yet.

SUMMARY

Persistent memory for Claude Code. Survives compaction and session boundaries: anti-patch reconcile-on-write with LLM-judged de-duplication, a forced PROGRESS.md handoff, a live PLAN.md anchor with an enforced directive ledger, FTS5 search, MCP tools and a GUI. Pure stdlib Python, zero runtime dependencies.

README.md

English · 简体中文

cc-memory

Persistent memory for Claude Code.
Your project's decisions, results, bugs and plans survive compaction, session
boundaries, and closed terminals — and the next session is forced to read
them before it does anything.

version
license
python
dependencies
release gates
platform


Table of contents


The problem

Claude Code compacts a conversation when the context window fills. Whatever
was in the discarded turns is gone: the decision you made three hours ago, the
benchmark number you measured, the bug you already fixed once, the constraint
you stated and had to state again. Sessions that simply end — terminal closed,
laptop shut — lose the same thing, silently.

The usual workarounds do not survive contact with a long project:

Workaround Why it fails
A hand-written NOTES.md Nobody updates it under deadline; it goes stale and starts lying
Pasting context back in each session Manual, lossy, and costs the tokens you were trying to save
Bigger context windows Delays the compaction; does not remove it
Append-only memory files The same fact gets restated every session and stacks up; the file becomes noise

cc-memory attacks all four. It captures structured memories at every
conversation boundary, reconciles each new fact against what is already
stored instead of appending, and emits a <system-reminder> at session start
that requires the next Claude to read the handoff document before it
responds.


How it works in 60 seconds

┌──────────────────────── your Claude Code session ────────────────────────┐
│                                                                          │
│  UserPromptSubmit ──▶ create memory/ on first contact, count the turn,    │
│                       seed "what the user asked for" on turn 1           │
│                                                                          │
│  PostToolUse     ──▶ live plan anchor (ExitPlanMode → captured plan,      │
│                       TodoWrite → step sync, edits → drift counters)      │
│                       + one observation row per observed tool call        │
│                                                                          │
│  Stop            ──▶ Haiku reads this turn's observations and writes      │
│                       memories · patches PROGRESS.md · enforces the plan  │
│                                                                          │
│  PreCompact      ──▶ sync leg  : extract from a bounded transcript window │
│                       → reconcile → FULL-REWRITE PROGRESS.md → archive    │
│                       async leg: LLM consolidation, off the blocking path │
│                                                                          │
│  SessionStart    ──▶ inject topics + critical memories + timeline, then   │
│                       FORCE: "Read memory/PROGRESS.md before responding"  │
└──────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
                     <project>/memory/memory.db   (SQLite + FTS5)
                     <project>/memory/PROGRESS.md (handoff, full-rewrite)
                     <project>/memory/PLAN.md     (live plan anchor)
                     <project>/memory/MEMORY.md   (browsable index)

Everything is project-local. memory/ lives inside your repository, is
git-ignored by a .gitignore cc-memory writes itself, and never leaves your
machine except for the extraction call to Anthropic (which you can scope with
<private> tags, or switch off per-project entirely).


Quick start

Install — marketplace (recommended)

claude /plugin marketplace add skymanbp/cc-memory
claude /plugin install cc-memory

Install — from a local checkout

git clone https://github.com/skymanbp/cc-memory.git
claude /plugin marketplace add ./cc-memory
claude /plugin install cc-memory

Install — standalone, no marketplace

python cc-memory/cc_memory/ui/installer.py          # GUI
python cc-memory/cc_memory/ui/installer.py --cli    # headless

Or download cc-memory-installer.exe from
Releases on Windows.

Then

Nothing. Per-project initialization is automatic: the first message you send in
a project creates <project>/memory/ and its database. Verify with:

/cc-mem status

To opt a directory out completely, list it in excluded_projects — see
Configuration.


Feature index

1 · Memory capture

Feature What it does
AI-judged extraction Haiku reads the conversation and returns structured {category, content, importance} records — not keyword scraping
Regex fallback A project-neutral pattern layer runs when no credential is available, so capture never depends on the network
Optional local fallback Ollama backend, opt-in via ccl.enabled (default false)
Two capture points Stop captures per turn from that turn's tool observations; PreCompact captures from the transcript before context is destroyed
Bounded transcript reads A head+tail window (40 records + 32 MiB) instead of the whole file — a 2.11 GiB transcript loads in 1.66 s instead of 88 s, so the hook is never killed mid-write
Newest-first summarisation The extraction budget fills from the most recent record backwards; filling from the oldest pinned every extraction to a session's opening hours
Seven categories decision · result · config · bug · task · arch · note
Five importance levels 1 noise → 5 critical / never forget
Three project modes code · research · writing — each with its own observed-tool set and injection priority (/cc-mem mode)
Language-agnostic content Extraction and resume-signal detection recognise English and Chinese by design; stored memories may be in any language

2 · Memory quality — the anti-patch contract

The differentiator. Most memory tools append; appending is what turns a memory
store into noise.

Feature What it does
Reconcile-on-write Every save path routes through one writer, which decides MERGE (overwrite a near-identical row in place), SUPERSEDE (archive the old, link the new via supersedes_id), or INSERT
Similarity substrate Trigram-Jaccard, with character bigrams inside CJK runs — plain trigrams collapse on Chinese, scoring a one-character correction at 0.4545 and filing every correction as a new contradictory fact
LLM-judged semantic de-dup The same fact reworded each session scores low on trigrams; a second pass nominates candidate groups by word-Jaccard and has Haiku confirm same-fact before merging
Obsolescence detection Names {stale, current} pairs with a temporal guard, so a historical action cannot obsolete a live fact
Walkable history supersedes_id forms a DAG (cycles refused, first lineage fact preserved with COALESCE); /cc-mem supersedes <id> walks it
Nothing is ever deleted Archival is is_active=0, always recoverable. /cc-mem archive is the supported way to retire a fact discovered to be wrong
Provenance preserved Tags are unioned with the surviving row's, never replaced, and capped

3 · Session handoff — PROGRESS.md

Feature What it does
Single source of truth memory/PROGRESS.md is always full-rewritten from one SQL row, never appended — it cannot go stale or self-contradict
Forced read SessionStart emits a <system-reminder> requiring the next Claude to read it before responding
Eleven user-facing fields current request · done · in-flight · blocked · open todos · plan · critical context · files touched · transcript pointer · updated-at · trigger type
Four writers, one contract PreCompact full-overwrites; Stop patches files-touched per turn; UserPromptSubmit seeds the request on turn 1; SessionStart fills only still-empty fields
Per-session annotation The row records which session is speaking and when it started
Killed-run visibility A start marker survives a timeout kill, so a compaction that died mid-write is provable rather than invisible
Injection observability .last_inject.json records exactly what was injected; /cc-mem inject-show dumps ground truth and /cc-mem inject-usage reports whether Claude actually read it

4 · Plan and intent — PLAN.md and the directive ledger

Two different things, deliberately kept apart: a plan step is a unit of
execution and dies when the plan is replaced; a directive is a unit of user
intent and outlives every plan.

Feature What it does
Live plan anchor memory/PLAN.md is full-rewritten from the plan_active row; ExitPlanMode output is captured automatically
Two shipped subagents plan-refiner normalises a raw plan into structured JSON; plan-guardian does a read-only ≤150-word drift check
Mechanical step sync TodoWrite events sync step statuses by title similarity — no LLM, no drift
Drift counters Edits bump a counter; a sensitive Bash call (git push, rm -rf, deploys) bumps it by 20
Mandatory carryover gate Replacing a plan requires every unfinished step to be auto-carried or explicitly dispositioned with a reason. There is no force flag, by design
Success-criteria advisory Criteria that vanish in a replacement are named — a gate that covers only steps says nothing about the rest
Append-only plan history Every outgoing plan is archived to memory/.plan_history/
Directive ledger /cc-mem directive-add records what the user demanded. Re-stating the same slug bumps times_stated on one row — repetition is an importance signal a plan cannot express
Evidence-gated closure /cc-mem directive-close refuses without --evidence: a commit, a file:line, or a gate name. A directive closed on an assertion is the failure the ledger exists to prevent
Stop enforcement The Stop hook can refuse to end a turn while a plan sits unrefined, a live plan is undrift-checked, or an active directive has gone idle — with a guaranteed escape budget and the kill switch CC_MEMORY_PLAN_ENFORCE=0

5 · Search and retrieval

Feature What it does
FTS5 full-text search /cc-mem search "auth flow", with LIKE ? ESCAPE fallback and clamped limits
Topic summaries Memories roll up into topics, refreshed by consolidation
Keyword vocabulary Project vocabulary by frequency, grown across sessions
Layered injection SessionStart injects topics + critical memories + a recent timeline + a PROGRESS preview, under per-layer budgets
Auto-fresh index memory/MEMORY.md is regenerated after every batch write
Corruption scan /cc-mem encoding-check finds U+FFFD damage across the text tables; --apply quarantines, recoverably

6 · Interfaces

Interface Entry point What you get
Slash command /cc-mem <sub> 32 subcommands, path-agnostic, resolves --project . for you
Shell CLI cc-memory / cli/mem.py The same 32 subcommands outside Claude Code
Plan queue CLI cc-memory-plan / cli/plan.py 12 subcommands for a draft → ready → executing → done task queue
MCP server cc_memory/mcp/server.py 8 tools over JSON-RPC 2.0 stdio, registered inline in the plugin manifest
Desktop dashboard /cc-mem dashboard Tkinter GUI, 7 tabs: Memories · Plans · Sessions · Keywords · SQL Console · Stats · Progress/Plan
Web viewer /cc-mem serve Loopback-only browser UI: browse, search, and add memories
Skills /ccm-load, /save-memories One-shot activation + bootstrap; manual save through the anti-patch writer
Subagents plan-refiner, plan-guardian Shipped in the plugin, discoverable under both install layouts

7 · Privacy and safety

Feature What it does
Project opt-out excluded_projects — a listed directory and everything beneath it gets no memory/, no database, no observations, no extraction, no injection. Enforced by every hook and by the MCP server, on the raw cwd, before the project-root anchor
Fails closed A config.json that exists but cannot be used excludes every project and logs why, rather than guessing "not excluded" and storing data irreversibly
<private> spans Text between <private> tags is stripped before both the Anthropic call and the database — linear-time, no cap, and a dangling open tag drops the remainder rather than leaking it
Authority-marker neutralisation Stored content is escaped, never interpolated raw, on the write path and on every render path — a memory cannot forge a <system-reminder> into your next session
Read-only SQL /cc-mem sql refuses every write statement, including the PRAGMA name(value) setter form
Loopback-only web viewer No CORS header, Origin and Host both enforced (DNS-rebinding), JSON content-type required on POST, bounded header and body phases, capped concurrency
MCP schema enforcement tools/call arguments are validated against the advertised inputSchema and refused with -32602 rather than coerced
No telemetry Nothing is sent anywhere except the extraction/consolidation calls to Anthropic (or your local Ollama), using your existing Claude Code credential

8 · Reliability engineering

Feature What it does
Zero runtime dependencies Pure Python standard library. PyInstaller is build-time only
Atomic artifact writes One writer — tmp + os.replace, with a wall-clock retry budget. Contract: replace completely, or raise; never truncate
Project-root anchoring cwd follows the agent's cd; a resolver walks an ancestor chain (database → CLAUDE_PROJECT_DIR → project markers) so a stray database is never born in a subdirectory. Containers of projects and dependency trees are never candidates
One shared hook entry ladder stdin parse → opt-out → root anchor, implemented once; per-hook policies stay per-hook
Bounded LLM wall-clock Every LLM-calling hook passes an absolute deadline, not just a per-leg timeout, so it cannot overrun the host's hard hook timeout
Off the blocking path Consolidation runs as an async PreCompact leg under a budget gate, so it can never surface as Hook cancelled
Scoped to the project Every command that touches a table scopes it by project_id — one database file legitimately holds several projects
10 release gates Three doc gates, four test suites, compileall, a pyproject parse, and version-site agreement. See Release gates
A falsification register Every registered fix is reverted on a temporary copy to prove its gate goes red. A gate that cannot fail is a gate that is lying

Reference

/cc-mem — 32 subcommands

Inside Claude Code (path-agnostic — the wrapper resolves the plugin root):

# ── state and health ───────────────────────────────────────────────────────
/cc-mem status                      Full health check (hooks, DB, API key, PROGRESS)
/cc-mem stats                       Memory counts + supersede-chain count
/cc-mem schema                      Live SQLite schema (tables, indexes, migrations)
/cc-mem mode [code|research|writing] Show or set the project mode
/cc-mem summary                     Latest session summary
/cc-mem sessions                    Compaction history with archive paths
/cc-mem observations                Raw PostToolUse rows awaiting extraction

# ── reading memory ─────────────────────────────────────────────────────────
/cc-mem search "<query>"            FTS5 search
/cc-mem list [category]             Recent memories, optionally by category
/cc-mem topics                      Topic summaries
/cc-mem keywords                    Project vocabulary by frequency
/cc-mem supersedes <id>             Walk a memory's supersede chain
/cc-mem sql "<SELECT ...>"          Read-only query (writes refused)

# ── writing memory ─────────────────────────────────────────────────────────
/cc-mem add <category> "<text>" [--importance N]   Anti-patch upsert
/cc-mem archive <id>... [--supersedes ID]          Retire a WRONG fact (recoverable)
/cc-mem consolidate                 Full LLM-backed consolidation
/cc-mem cleanup                     Lightweight no-LLM cleanup + MEMORY.md regen
/cc-mem encoding-check [--apply]    U+FFFD corruption scan

# ── handoff ────────────────────────────────────────────────────────────────
/cc-mem progress                    Regenerate memory/PROGRESS.md and print it
/cc-mem inject-show                 What the last SessionStart injected
/cc-mem inject-usage                Did Claude actually read PROGRESS.md / MEMORY.md

# ── live plan anchor ───────────────────────────────────────────────────────
/cc-mem plan-status                 Counters + freshness summary
/cc-mem plan-show                   Regenerate + print memory/PLAN.md
/cc-mem plan-set --raw "<text>"     Capture a raw plan, mark needs_refine
/cc-mem plan-set --raw-file FILE    Same, from a file
/cc-mem plan-set --from-refiner     Store structured JSON from stdin
/cc-mem plan-check                  Reset drift counters + emit guardian hint
/cc-mem plan-replan                 Re-arm needs_refine on the stored raw
/cc-mem plan-clear --reason "<why>" Drop the active plan (reason required if unfinished)

# ── directive ledger ───────────────────────────────────────────────────────
/cc-mem directive-list [--status active|done|superseded|dropped|all]
/cc-mem directive-add <slug> --demand "..." [--quote "..."] [--kind ...] [--times N]
/cc-mem directive-close <slug> --evidence "<commit|file:line|gate>"

# ── interfaces ─────────────────────────────────────────────────────────────
/cc-mem dashboard                   Launch the Tkinter GUI
/cc-mem serve [--port N]            Launch the loopback web viewer

Full per-subcommand semantics: commands/cc-mem.md.

Outside Claude Code:

# NOTE: $HOME, not ~. Bash expands a tilde BEFORE parameter expansion and does
# not rescan the result, so a ~ stored inside a variable stays a literal.
M="python $HOME/.claude/hooks/cc-memory/cli/mem.py --project ."   # flat install
$M status
$M search "auth flow"

cc-memory-plan — the plan queue

A task queue in the same database, distinct from the live plan anchor.

P="cc-memory-plan --project ."       # or python .../cli/plan.py --project .

$P add "Task A" "Task B" "Task C"    # append drafts
$P list                              # show the queue
$P reorder <id> <position>           # move a task
$P evaluate                          # draft → evaluating
$P set-eval <id> "<verdict>"         # record a feasibility verdict
$P approve --all                     # evaluating → ready
$P exec --next                       # ready → executing, print the plan text
$P done <id> "<result>"              # → done
$P fail <id> "<why>"                 # → failed
$P skip <id> "<why>"                 # → skipped
$P status                            # queue summary
$P clear                             # drop done/failed/skipped

Flow: draft → evaluating → ready → executing → done | failed | skipped.
exec spawns nothing — it flips status and prints the plan text plus the
done command to run afterwards. Every subcommand that names an id resolves it
within --project first and exits 1 on an unknown or foreign id.

MCP tools

8 tools over JSON-RPC 2.0 stdio. The server forces UTF-8 with LF newlines on
both stdin and stdout itself — no PYTHONUTF8 / PYTHONIOENCODING env block
is required
.

Tool Purpose
memory_search FTS5 search, compact results
memory_get_details Batch fetch full details by id
memory_add Add through the anti-patch writer
memory_stats Project statistics
memory_topics Topic summaries (bounded)
memory_recent Recent memories with filters
progress_get Read the PROGRESS state as structured fields
progress_regenerate Force-rewrite memory/PROGRESS.md from SQL

Marketplace / dev checkout — nothing to do. .claude-plugin/plugin.json
ships the registration inline.

Standalone install — register by hand. Note the absent cc_memory/ path
segment in the flat layout:

// <project>/.mcp.json, or the user-scoped equivalent
{
  "mcpServers": {
    "cc-memory": {
      "command": "python3",
      "args": ["<HOME>/.claude/hooks/cc-memory/mcp/server.py"]
    }
  }
}

Configuration

config.json in your install root — flat: ~/.claude/hooks/cc-memory/config.json;
marketplace / dev checkout: <plugin-root>/cc_memory/config.json.

Every key in this file is read by code. Inert tunables were deleted rather
than left in place, because editing one that does nothing looks like it does
something.

Key Default Meaning
version 2.11.1 Last-resort fallback for a flat install predating core/version.py, which is canonical
consolidation.auto_interval_sessions 5 Sessions between async consolidation runs
ccl.enabled false Local Ollama fallback — opt-in
ccl.ollama_url http://localhost:11434 Ollama endpoint
ccl.local_model ccl-9b Local model name
excluded_projects [] Absolute paths that opt OUT entirely. The only opt-out; there is no per-project override file
notes In-file documentation, including which module reads each value

Everything else is a module constant, documented in notes.removed_keys:
writer thresholds in llm/memory_writer.py, injection budgets in
hooks/session_start.py, the idle-reorg interval in core/idle.py, the
per-mode observation skip-list in core/modes.py, the web viewer's default
port in ui/web_viewer.py. MCP registration is the mcpServers block in
.claude-plugin/plugin.json, not a config key.

Environment variables

Variable Effect
ANTHROPIC_API_KEY Preferred credential; falls through to the Claude Code OAuth token when absent or dead
CLAUDE_PROJECT_DIR Consulted by the project-root resolver when it names a directory in the ancestor chain
CC_MEMORY_PLAN_ENFORCE=0 Kill switch for Stop-hook plan enforcement

Per-project files

<project>/memory/
├── memory.db                   SQLite (WAL) — the source of truth
├── MEMORY.md                   browsable index, refreshed after every write
├── PROGRESS.md                 handoff; full-rewritten from the `progress` row
├── PLAN.md                     live plan anchor; from the `plan_active` row
├── .gitignore                  written by cc-memory; migrates on existing installs
├── .last_save.json             status + trigger of the last PreCompact
├── .last_inject.json           what SessionStart injected (observability)
├── .last_consolidation.json    interval marker for the async leg
├── .consolidation.lock         prevents overlapping async workers
├── .pre_compact_attempt.json   start marker; survives ⇒ the last run was killed
├── .plan_raw.md                last raw ExitPlanMode capture
├── .plan_history/              append-only archive of replaced / cleared plans
├── sessions/YYYY/MM/           per-session archives
└── topics/                     reserved for per-topic exports

Database schema

12 tables in one project-local SQLite file:

Table Holds
projects One row per project root
sessions Compaction / session history with archive paths
memories The facts, with supersedes_id, content_hash, is_active
topics Rolled-up topic summaries
keywords Project vocabulary by frequency
observations PostToolUse events, cleaned after extraction
session_summaries A structured summary per session
progress One row per project — the source of truth for PROGRESS.md
plan_active One row per project — the source of truth for PLAN.md
plans The plan queue (cc-memory-plan)
directives The user-intent ledger (/cc-mem directive-*)
_migrations Applied schema migrations

Hook table

Six hook commands across five Claude Code events, declared in
hooks/hooks.json:

Event Script Timeout Job
UserPromptSubmit hooks/user_prompt.py 8 s Auto-init memory/, count the turn, seed the request on turn 1
PostToolUse hooks/post_tool_use.py 8 s Live plan anchor in every mode, then one observation row per observed tool
Stop hooks/stop.py 22 s Haiku observer, per-turn PROGRESS patch, idle reorg every 5 turns, plan enforcement
PreCompact (sync) hooks/pre_compact.py 120 s Extract → reconcile → full-rewrite PROGRESS.md → archive
PreCompact (async) hooks/consolidate_async.py 300 s Budget-gated consolidation, off the blocking path
SessionStart hooks/session_start.py 15 s Inject layered context + the forced <system-reminder>

Hook contract, never violated: hooks never write to stderr (Claude Code renders
stderr as error UI), never raise, and always exit 0.


Architecture

Full detail in docs/ARCHITECTURE.md
(简体中文). The three hard contracts are specified in
docs/CONTRACTS.md (简体中文):

Two install layouts, both supported. A marketplace / dev checkout keeps the
nested <plugin-root>/cc_memory/… shape. The standalone installer lays the
package FLAT under ~/.claude/hooks/cc-memory/core/, hooks/, llm/,
cli/, mcp/, ui/ directly under it, with no cc_memory/ path segment.
Any code or documentation that probes for an install must accept both.


Development

Repository layout

cc-memory/
├── .claude-plugin/          plugin.json (+ inline mcpServers) · marketplace.json
├── .github/                 CI running every release gate · issue + PR templates
├── agents/                  plan-refiner.md · plan-guardian.md
├── commands/                cc-mem.md — the /cc-mem slash command
├── hooks/hooks.json         hook declarations (6 commands / 5 events)
├── skills/                  ccm-load/ · save-memories/
├── cc_memory/               the Python package
│   ├── core/                db · extractor · consolidate · plan · progress · privacy
│   │                        modes · roots · atomic · markers · textsim · auth · …
│   ├── hooks/               _entry (shared ladder) + the six hook entry points
│   ├── llm/                 ccl_backend · memory_writer · parse
│   ├── cli/                 mem.py · plan.py
│   ├── mcp/                 server.py
│   └── ui/                  installer · dashboard · web_viewer
├── docs/                    ARCHITECTURE.md · CONTRACTS.md (+ .zh.md siblings)
├── scripts/                 build_exe.py — PyInstaller build
├── tests/                   4 suites + run_gates.py (one command, every gate)
├── tools/                   citation_check · doc_claims · contracts · falsify_fixes · i18n_check
├── CLAUDE.md                project instructions for Claude Code
├── CHANGELOG.md · README.md · README.zh.md · LICENSE · pyproject.toml

Release gates

Ten gates, all pure stdlib — no pytest, no pip dependencies. Run them all with
one command:

python tests/run_gates.py           # runs all 10, prints a table, exits nonzero on any red
python tests/run_gates.py --list    # show what each gate is

Or individually:

python -m compileall -q cc_memory tests tools
python -c "import tomllib,pathlib;tomllib.loads(pathlib.Path('pyproject.toml').read_text(encoding='utf-8'))"
python tests/smoke_test.py                    # end-to-end + the three doc gates + version agreement
python tests/test_plan_carryover.py           # the carryover gate
python tests/test_surfaces.py                 # installer · MCP · web viewer · opt-out · anchoring
python tests/test_directive_enforcement.py    # the directive ledger + Stop enforcement
python tools/i18n_check.py                    # translation drift
python tools/citation_check.py                # every file.py:LINE citation in the tracked docs
python tools/doc_claims.py                    # prose counts vs the sets computed from the tree

Two more scripts are not gates, and are the ones to reach for when you
doubt a gate:

python tools/contracts.py       # print what the code currently says each set contains
python tools/falsify_fixes.py   # revert each registered fix on a COPY, assert its gate goes RED

Tests must use tempfile directories only and must remove them: all four
suites redirect HOME/USERPROFILE and TMPDIR/TEMP/TMP into a
sandbox before importing the package, assert Path.home() really moved, and
tear the sandbox down in a finally. An uncleanable leak is a test failure.

Build the executables

pip install pyinstaller
python scripts/build_exe.py
# → dist/cc-memory-installer.exe
#   dist/cc-memory-dashboard.exe

Contributing

See CONTRIBUTING.md. Security reports: SECURITY.md.


Troubleshooting

Symptom Cause and fix
Hooks never fire on Windows hooks/hooks.json invokes python3, and the python.org installer ships no python3.exe. Tick "Add Python to PATH" + "py launcher", or shim python3 → python
/cc-mem says the plugin is not found Both install layouts must be probed. Run /cc-mem status — it inspects the layout and reports which files are missing
Nothing is being extracted No credential. /cc-mem status checks it. Log in to Claude Code, or set ANTHROPIC_API_KEY
A memory/ appeared in a subdirectory A stray from before root anchoring. /cc-mem status lists every separate database below the project root with its memory count. A stray is reported, never merged or deleted — pin a genuinely nested project with a .ccm-root file
The plugin went completely silent A config.json that exists but cannot be parsed fails closed and excludes every project. SessionStart prints one line saying so; fix the JSON
Claude cannot end a turn Plan enforcement is blocking. Read the refusal — it names the condition and the fix. It always degrades to an advisory after its escape budget; CC_MEMORY_PLAN_ENFORCE=0 switches it off
Hook cancelled on compaction Fixed in v2.3.2 by moving consolidation to an async leg. If you still see it, file an issue with memory/.last_save.json
A memory is simply wrong /cc-mem archive <id> — reconciliation handles a restatement, archive handles a repudiation

What's new in v2.11.1

v2.11.0 shipped the enforcement engine with zero coverage — and shipped with
a RED gate.
The directive ledger added three CLI subcommands and
commands/cc-mem.md was never updated, so tests/smoke_test.py failed; and
because it is one sequential function, that first failing assert also hid the
one below it. Nothing caught either, because "run all ten gates" was a sentence
in a markdown file rather than an executable. It is one now:
python tests/run_gates.py, plus CI.

Six defects in the path that can refuse your turn, each reproduced first:

  • The escape budget could never release. write_marker never raises — it
    returns False — so the except OSError guarding the "cannot count, so do
    not block" case was dead code. On any temp directory the marker layer
    refuses, the hook blocked forever: measured [1,1,1,1,1,1,1,1] over eight
    consecutive Stops. An unbreakable block is worse than no block.
  • A stored directive reached Claude as a live authority marker. The block
    reason is fed back as a decision payload — higher authority than PROGRESS.md
    — and was the one renderer in its module that never escaped. Now escaped on
    the way in and the way out.
  • A refusal's stdout was not a JSON document, because a status line printed
    first.
  • A cleared plan enforced forever — the hook tested a row that
    plan-clear deliberately keeps as a tombstone.
  • A just-stated directive was reported idle for N turns, because idleness
    came from the plan's counter rather than the directive's own history.
  • Re-stating a directive erased its demand and quote — the single operation
    the ledger exists for.

Also: concurrent directive writes are serialised; the install health check now
derives its required-module list from the hooks' import graph instead of a
hand-kept list that had gone stale three times; and a .gitignore pattern that
silently took .github/ to zero tracked files is gated. Nine new falsification
cases, every one driven RED individually — two ran GREEN first, and the
checks were fixed rather than the cases.

Every earlier release is in CHANGELOG.md, which is the
single history of this project — this README documents what the software is,
not what it used to be.


Requirements

  • Python 3.8+ — standard library only, zero runtime dependencies
  • Claude Code with hooks support
  • Tkinter only for the desktop dashboard (the CLI, MCP server and web
    viewer do not need it)
  • PyInstaller only for building the executables
  • Windows: python3 must resolve to a Python 3 interpreter (see
    Troubleshooting)

Developed Windows-first; cross-platform by construction and exercised on POSIX
paths throughout.


Documentation map

Document What it is for
README.md · README.zh.md What this is, what it does, how to use it
docs/ARCHITECTURE.md · .zh Module map, data flow, install layouts, the i18n convention
docs/CONTRACTS.md · .zh The three hard contracts, in specification form
commands/cc-mem.md Every /cc-mem subcommand, with semantics
CLAUDE.md Instructions for Claude Code working on this repository
CHANGELOG.md The complete version history
CONTRIBUTING.md · SECURITY.md How to contribute; how to report a vulnerability

English is the canonical skeleton; each *.zh.md is a drift-tracked sibling
bound to a normalised hash of its English source, and tools/i18n_check.py
turns the suite red when one drifts. Memory content is language-agnostic —
only the project's own documentation follows this convention.


License

MIT © skymanbp


Keywords — Claude Code plugin · Claude Code memory · persistent memory
for LLM agents · agent long-term memory · context window management ·
conversation compaction recovery · session handoff · AI coding assistant memory
· Anthropic Claude · MCP server · Model Context Protocol · SQLite FTS5 memory
store · retrieval for coding agents · prompt-injection hardening · pure-stdlib
Python plugin.

Reviews (0)

No results found