Skip to content

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Claude Code Stack

Persistent memory and real code intelligence for Claude Code — and the one rule that keeps them from competing.

platform license local verified

English · Русский · 中文 · Español


Four tools that give Claude Code memory surviving across sessions and a structural map of your code — plus the global CLAUDE.md that assigns each one its job. Installed and verified end-to-end; the gotchas are the part you actually want, since each entry cost real debugging time.

Contents

The stack

Tool What it gives you Runs as
claude-mem Cross-session conversational memory. Captures what happened and injects relevant past work at session start. Local worker + SQLite + Chroma
claude-mem-ollama-proxy Routes claude-mem's memory generation to Ollama Cloud, disables reasoning, and redacts secrets before anything leaves the machine. Local proxy on 127.0.0.1:11435
codebase-memory-mcp Persistent code knowledge graph — functions, call chains, routes, cross-repo links. Architecture answers in milliseconds. Native binary + daemon
serena Live LSP symbol navigation, exact references, and symbol-level editing. Language servers per project

How it fits together

flowchart LR
    CC["Claude Code session"]

    CC -->|"what happened before"| CM["claude-mem<br/>worker :37777"]
    CM --> PX["ollama proxy :11435<br/>reasoning off · secrets redacted"]
    PX ==>|"the only traffic that leaves"| OC[("Ollama Cloud")]

    CC -->|"where is X · who calls X<br/>architecture · impact"| CBM["codebase-memory-mcp<br/>daemon · UI :9749"]
    CBM --> GR[("code graph<br/>local SQLite")]

    CC -->|"exact refs · edits · types"| SR["Serena"]
    SR --> LS["language servers"]

    style OC fill:#f9d5d5,stroke:#c96
    style GR fill:#d5e8d4,stroke:#82b366
    style LS fill:#d5e8d4,stroke:#82b366
Loading

Everything green stays on your machine. The only outbound traffic is memory generation, and it passes through the proxy that strips credentials first.

Why two code tools

Installing a code graph and an LSP server without a rule makes the agent thrash: one says "read the graph", the other says "use LSP". CLAUDE.md settles it in one line — the graph answers questions, Serena makes changes:

Question Tool
Where is this? Who calls it? How is it built? What breaks if I change it? graph — instant, covers every indexed repo, works across repos
Exact references before touching a symbol · the edit itself · type errors after Serena — reads current on-disk truth, and can modify code

If the graph and the files disagree, the files win — re-index instead of trusting a stale answer.

Install

Order matters: the proxy edits ~/.claude-mem/settings.json, so claude-mem has to exist first.

1 · claude-mem

npx claude-mem install

Choose the Worker runtime. Any OpenAI-compatible provider works — the proxy overrides it in step 2 — so the cheapest path is to paste an Ollama key from ollama.com/settings/keys.

The installer may end with Fatal error: ENOENT ... .install-version plus an npm ERESOLVE warning. Both are harmless — see gotchas.

2 · Ollama proxy

git clone https://github.com/limeflash/claude-mem-ollama-proxy.git
cd claude-mem-ollama-proxy
.\windows\install.ps1

macOS/Linux: ./macos/install.sh. Registers an at-logon task (no admin), points claude-mem at http://127.0.0.1:11435/v1, defaults to glm-5.3-flash. Another model: -Model "gpt-oss:120b" — list them at https://ollama.com/v1/models.

It injects reasoning_effort: "none". Without that a reasoning model returns its answer in reasoning, leaves content empty, and claude-mem silently stores nothing. For models that ignore that flag entirely, see Choosing the model — the proxy routes them through Ollama's native endpoint instead.

3 · codebase-memory-mcp

Invoke-WebRequest -Uri https://raw.githubusercontent.com/DeusData/codebase-memory-mcp/main/install.ps1 -OutFile install.ps1
Unblock-File .\install.ps1
.\install.ps1

Native binary — no API key, no runtime. Semantic search uses embedded embeddings; nothing leaves the machine. It auto-configures every agent CLI it detects.

codebase-memory-mcp daemon start
codebase-memory-mcp cli index_repository --repo-path C:\path\to\repo
codebase-memory-mcp cli list_projects

Index each repo separately. An umbrella folder full of node_modules and build output produces one useless blob instead of clean per-project graphs.

4 · Serena

uv tool install --from git+https://github.com/oraios/serena serena-agent
claude mcp add serena -s user -- serena start-mcp-server --context claude-code --project-from-cwd --enable-web-dashboard False

Pin a release with @v1.7.0 after the repo URL. To upgrade later, add --force; if it fails to remove the old tool directory, see gotchas.

5 · Global instructions

Copy CLAUDE.md to ~/.claude/CLAUDE.md. It loads into every session automatically, so the rules apply without you saying anything.

Keep it in English even if you work in another language — it is configuration read by the agent, not documentation for you.

Verify

Get-ScheduledTask -TaskName claude-mem-ollama-proxy
Get-Content "$env:USERPROFILE\.claude-mem-proxy\proxy.log" -Tail 5
# healthy: POST /v1/chat/completions -> 200 [reasoning_effort=none]

Invoke-WebRequest http://localhost:37777 -UseBasicParsing | Select-Object StatusCode

codebase-memory-mcp daemon status
codebase-memory-mcp cli list_projects        # UI: http://127.0.0.1:9749

In Claude Code, /mcp should list serena and codebase-memory-mcp. MCP servers connect only at startup — restart Claude Code after installing.

Prompts to paste

Copy-paste straight into a session. Language does not matter — write in whatever you normally use. More in PROMPT.md.

Orientation — first message in a new repo

This machine has two code-intelligence servers. Use them instead of grepping the tree or reading whole files.
The graph (codebase-memory-mcp) answers questions. Serena makes changes.

1. Call list_projects first. If this repo is not indexed, index it with index_repository before anything else.
   If it is indexed but anything big happened outside this session — git pull, branch switch, rebase, or the
   daemon was down — re-index it as well. The watcher only keeps the graph fresh while it is actually running,
   and a stale graph fails silently.
2. For "where is X / who calls X / how is this built / what breaks if I change X" use get_architecture,
   search_graph, trace_path, query_graph, get_code_snippet. Semantic search is a mode of search_graph
   (semantic_query=["a","b"]), not a separate tool. Do not fall back to Grep/Glob for structural questions.
3. Serena holds one project at a time. If the file you are about to edit lives outside this session's working
   directory, call activate_project("<repo path>") first — otherwise the wrong language servers are running.
   Then get exact references with find_referencing_symbols, edit with replace_symbol_body /
   insert_after_symbol / rename_symbol / safe_delete_symbol, and run get_diagnostics_for_file.
4. If the graph and the files disagree, the files win — re-index rather than trust a stale answer.

Start with a short architecture summary of this repo from the graph, and tell me if anything above was unavailable.

That last sentence matters: without it, a missing MCP server turns into the agent quietly grepping and pretending everything is fine.

Health check — when something feels off

Check my setup and report what is actually broken, not what should be there:
- is the codebase-memory-mcp daemon active, and how many projects are indexed?
- is serena connected?
- does the claude-mem worker answer on http://localhost:37777?
- does ~/.claude-mem-proxy/proxy.log show recent "-> 200 [reasoning_effort=none]" lines?
For anything failing, give me the cause and the fix — do not just restart things.

Set up a new machine

Read the README in this repo and set up the whole stack on this machine, in the order given.
Stop and tell me before anything that needs a paid key. When done, run the health check and show the result.

Index a batch of repos

Index every git repository under <path> into the code graph. Index each repo separately — do not index a
parent folder containing several of them — and skip empty stubs, archives, and folders that are only build
output or datasets. Then show me the project list with node and edge counts.

Keeping claude-mem from blocking you

claude-mem's UserPromptSubmit hook is synchronous. When the worker is unreachable it exits non-zero and Claude Code blocks your prompt — observed for real, 77 consecutive rejected prompts. The plugin marks its PostToolUse, PreToolUse and Stop hooks "async": true, which cannot block; the one hook standing between you and your keyboard is not.

It gets stuck because the failure is self-perpetuating: the worker dies, its listening socket on :37777 survives (inherited by a live child process), the launcher sees the port occupied and logs Port already in use, refusing to start duplicate, nothing answers health checks, so every hook fails — forever.

Two layers, in watchdog/:

1. Hook hardening — the guarantee. harden-hooks.js wraps the blocking hooks in a subshell:

node watchdog/harden-hooks.js ~/.claude/plugins/cache/thedotmack/claude-mem/<version>/hooks/hooks.json

An exit 1 inside the original command now terminates only the subshell, and the trailing exit 0 still runs — so a dead worker costs you some observations instead of your ability to type. Idempotent; re-apply after a plugin update, since the plugin cache is overwritten.

2. Watchdog — the recovery. claude-mem-watchdog.ps1 as a scheduled task, every 5 minutes. It restarts the Ollama proxy on :11435 when that is down — the proxy runs in a console window, so a stray Ctrl+C kills it, after which the worker still reports healthy while every generation request quietly fails.

For the worker it acts on one condition only: :37777 is bound but nothing answers. That is the real deadlock. A worker that is simply absent is left alone, because claude-mem shuts its generator down after ~3 minutes idle (Idle timeout reached, triggering abort to kill subprocess) and a hook lazy-spawns it on the next prompt. Getting this wrong is expensive — see Two weeks in.

It exits immediately when no agent client is running, and again if you disabled the plugin — no client means no consumer, and a disabled plugin is a decision, not a fault. A global mutex covers scheduled and manual runs alike: two copies once fought and one killed the healthy daemon the other had just started.

Launch it through run-hidden.vbs, not directly. Task Scheduler running a console app in an interactive session flashes a window on every run. Every five minutes, on top of whatever you are doing — including full-screen games, which it pulls you out of. wscript has no console of its own and Run(..., 0, False) starts the child hidden:

$vbs = "$env:USERPROFILE\.claude-mem-watchdog
un-hidden.vbs"
$a = New-ScheduledTaskAction -Execute wscript.exe -Argument "//nologo `"$vbs`""
Set-ScheduledTask -TaskName claude-mem-watchdog -Action $a

-WindowStyle Hidden alone does not do it, and the clean fix — an S4U principal that never touches the desktop — needs admin rights.

Keeping the code graph alive

The codebase-memory-mcp daemon needs a different mechanism, and this is the trap: its daemon and CLI find each other through a named pipe whose name is a hash of the launching context.

started by Task Scheduler : cbm-daemon-bc0bed48…
started from a session    : cbm-daemon-2a438ffc…

A daemon launched from a scheduled task therefore runs fine, holds the UI port, and is permanently invisibledaemon status reports "not running" next to a live process. The CLI then spawns a throwaway daemon per command, those race, the registry wedges, and the projects list comes back empty. The project .db files are never affected; only the registry is.

So it is kept alive by a Claude Code SessionStart hook instead, which runs in the context where the pipe name matches: ensure-cbm-daemon.ps1 behind the fire-and-forget wrapper cbm-daemon-ensure.cmd. Copy both to ~/.claude/hooks/ and register the wrapper on every SessionStart matcher in ~/.claude/settings.json:

{ "type": "command", "command": "cmd.exe /d /v:off /s /c '\"\"%USERPROFILE%\\.claude\\hooks\\cbm-daemon-ensure.cmd\"\"'", "timeout": 10 }

The wrapper returns in ~40 ms with exit code 0 — it detaches the real work, so a slow or failing repair can never delay or block a session. And because it only runs when a session starts, the daemon exists exactly when something needs it. It refuses to resurrect the plugin if you disabled it, never touches your Claude Code sessions, and skips the port owner unless it is genuinely a claude-mem worker — the .claude-mem-proxy process matches a naive *claude-mem* filter and must not be killed.

$s = "$env:USERPROFILE\.claude-mem-watchdog\watchdog.ps1"
$a = New-ScheduledTaskAction -Execute powershell.exe -Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$s`""
$t = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1) -RepetitionInterval (New-TimeSpan -Minutes 5)
Register-ScheduledTask -TaskName claude-mem-watchdog -Action $a -Trigger $t

When the port owner PID is dead, the handle was inherited by a child that outlived the worker. In practice that child is claude-mem's own Chroma stack — chroma-mcp.exe and its python workers — orphaned when the worker died, not an editor session. The watchdog kills those, then proves the port is genuinely reclaimed with a bind test before respawning; a LISTENING row in netstat is not proof either way. If something it cannot identify still holds the port, it logs that and stops rather than killing processes at random.

Choosing the model

The memory is only as good as the model that writes it, so this was measured rather than guessed: eight real sessions from this machine, ground truth known, scored on whether the summary kept the fact that mattered.

GLM-5.3-Flash DeepSeek-V4-Flash
Facts kept 10/10 7/10
Invented connections 0 0
Tokens per call ~510 ~44
Latency 4.9 s 0.9 s

What DeepSeek dropped is the interesting part. It lost the number 266 from "273 attempts, 266 failures, 5 successes" and then wrote a summary that contradicted itself — "273 restart attempts yielded 5 successes, all failing". It recorded that a scheduled task "failed with Access denied" but not that the S4U principal was the thing that failed, so the searchable term is gone. And on the session that cost two hours to understand it said the watchdog "treats that as a crash" without recording that the shutdown was by design — which was the entire finding.

Latency does not matter here: generation runs in the background Stop hook, nothing waits on it.

Thinking models need the native endpoint

GLM will not work through the obvious path. Ollama's OpenAI-compatible /v1/chat/completions cannot accept the think parameter (ollama#15288, #15293), so a reasoning model narrates into content — 1300–1600 characters of "The user wants me to compress…" and never the requested format, in six attempts — or, with reasoning.enabled:false, returns content empty.

The proxy solves it by translating to the native /api/chat. Three details decide whether that works:

  • think: true, not false. Counter-intuitive: true puts deliberation in its own thinking field and leaves content for the answer. false merely inlines the same narration back into content.
  • Budget headroom (CMP_THINK_HEADROOM, default 1200). The caller's max_tokens budgets the answer, but the model spends tokens thinking first. Without headroom it dies mid-deliberation every time — the reason the naive attempt looked like the model was simply broken.
  • A fallback (CMP_THINK_FALLBACK, default deepseek-v4-flash:0731). A thinking model drains a usage quota faster, so it is first to be refused. On any 4xx/5xx the proxy retries with the cheap model: worst case is a weaker summary, never a missing one.

Selection is CMP_THINK_MODELS (default glm-*), so this opens every thinking model Ollama offers. A healthy log line looks like:

POST /v1/chat/completions -> 200 [native think] [thoughts dropped: 1349 ch]

What it actually costs

Nothing, on a subscription — and "tokens" is the wrong unit to reason in.

Ollama Cloud exposes an undocumented usage endpoint that settles this with real numbers instead of estimates:

curl -H "Authorization: Bearer $OLLAMA_API_KEY" https://ollama.com/api/usage

It returns limits.session and limits.weekly as 0–1 fractions plus per-model request counts, and activity for the last four weeks. POST /api/me (POST, not GET) returns the plan.

On the machine this repo documents — Pro plan, memory running continuously for two weeks:

Metered cost, 4 weeks $0.00000
Weekly quota used 0.5%
Projected on GLM (~2× drain) ~1%

Two things that make token-counting misleading here. Quota is weighted per model, not per token — in a controlled comparison GLM drained roughly 2× the quota per call while spending fewer tokens than DeepSeek. And the meter ignores small requests entirely: the weekly counter showed 271 requests where the proxy log had thousands.

So on a subscription, pick on quality. Token arithmetic only starts to matter on a per-token provider, or if /api/usage shows the weekly fraction actually climbing.

Two weeks in

Numbers from one machine running this stack continuously, 17–31 August 2026:

Memory database 5.5 MB → 51.9 MB
Observations recorded ~7,900
Generation calls through the proxy 7,383, of which 2 were not 200
Credentials stripped before leaving the machine 4,192
Proxy killed by a stray Ctrl+C 2 — the watchdog restarted both
Orphaned-socket deadlocks cleared 4

That 4,192 is the whole argument for the proxy. Every one of those was a password, token or basic-auth string sitting in a conversation that was about to be sent verbatim to a third-party model for summarising. The proxy replaced each with [SECRET:{type}] first.

Recall quality. Asked about work from minutes earlier, it is precise and specific — it had already recorded "scheduled task principal change to S4U blocked by access denied" and "foreign powershell window identified as Warp terminal's process" while that work was still happening. Asked to pin down one specific incident from two weeks back, it returned neighbouring material instead of the exact event. Recent recall is strong; long-range pinpointing is weaker, so treat it as a working memory, not an archive you can query like a database.

And what the watchdog got wrong. Misreading a healthy idle state as a fault is expensive:

273  claude-mem worker unhealthy on :37777
273    respawning worker from 13.15.0
266    worker still down after 45s      <- nothing was broken
  5    worker recovered

One pointless repair attempt every five minutes for two weeks, each flashing a console window over whatever was on screen — full-screen games included. The worker was never broken; claude-mem simply shuts its generator down when idle. Both fixes (act only on bound-but-unresponsive, launch through wscript) are in this repo.

Gotchas

Every one of these was hit for real.

Symptom Cause Fix
npx claude-mem install ends in Fatal error: ENOENT ... marketplaces\thedotmack\plugin\.install-version Cosmetic path bug — the file is written one level up Ignore. Check the plugin cache has node_modules and the MCP responds
…and an npm ERESOLVE tree-sitter conflict Redundant install of dev-only grammars; runtime deps already installed via bun Ignore
Memory generation costs a fortune Default paths bill per observation (Haiku ≈ $58/1k, OpenRouter ≈ $8/1k) Use the proxy — generation moves to your Ollama Cloud balance
claude-mem stores nothing, no error shown Reasoning model put text in reasoning, left content empty The proxy's reasoning_effort: "none"
codebase-memory-mcp install exits 1 and PATH is never registered One failing agent config aborts activation. A Hermes config at %LOCALAPPDATA%\hermes\config.yaml fails deterministically regardless of contents — issue #1656 Remove/rename that dir, or add the install dir to PATH by hand. Other agents configure fine
daemon status says "not running" while the UI on :9749 answers Competing daemons, usually from repeated install --force daemon stop, kill leftover codebase-memory-mcp.exe, daemon start once
The graph UI lists no projects, or daemon status says "not running" while a codebase-memory-mcp.exe is clearly alive and serving :9749 The daemon was started from a context whose pipe-name hash differs from the CLI's — Task Scheduler is the usual culprit. It runs and is never found, so every CLI call spawns a throwaway daemon and those race until the registry wedges Your data is fine — the per-project .db files are untouched. Kill every process flagged --cbm-daemon-internal (never the unflagged ones, those are session-owned MCP servers), then daemon start from a terminal inside a session. Automate it with the SessionStart hook
Graph answers look stale auto_watch=true refreshes indexed projects, but auto_index=false — new repos are never picked up Run index_repository once per new repo
Prompts stop working: A hook blocked your prompt … claude-mem worker unreachable for N consecutive hooks The worker died, its :37777 socket survived, the launcher refuses to spawn a duplicate, health checks fail — and the synchronous UserPromptSubmit hook blocks input. Self-perpetuating Disable the plugin to type again, then apply watchdog/. See the section above
A port shows a listener whose PID does not exist (taskkill: process not found) Orphaned socket — a child inherited the handle and outlived its owner. For claude-mem the culprit is its own chroma-mcp.exe + python workers, still running after the worker died Kill those helpers, then confirm with an actual bind ([System.Net.Sockets.TcpListener]) — netstat still lists the ghost until the last handle closes. No reboot needed
Serena fails with Cannot extract symbols from <file>. Active language servers: ['python'] on a TypeScript (or other) file Not missing language support. Serena holds one project at a time and binds to the session's working directory, so only that project's language servers are up activate_project("<repo path>"), then retry. Verified: activating a TS repo brings up the typescript server and symbol extraction works
Agent claims semantic_query / activate_project "do not exist" semantic_query is a parameter of search_graph, not a tool — so searching the tool list for it fails. activate_project does exist; a keyword tool-search just ranks it poorly Call search_graph(semantic_query=["a","b"]); select activate_project by exact name
detect_changes returns seed_symbols: 0 despite many changed files It diffs against base_branch (default main) or since — uncommitted working-tree changes resolve to no symbols Commit first, pass the right base_branch/since, or fall back to trace_path for blast radius
uv tool install --force fails: "failed to remove directory … reparse point … (os error 4395)" Misleading error — there is usually no reparse point. Stop every serena.exe first; if it persists, the directory needs a forced delete robocopy <empty-dir> <tool-dir> /MIR, then rmdir /s /q, then install again
Every plugin suddenly reads Disabled and cannot be re-enabled Something rewrote ~/.claude/settings.json with a UTF-8 BOMSet-Content -Encoding UTF8 does exactly that on PowerShell 5.1. A leading EF BB BF makes a strict JSON parser reject the entire file, so nothing in it applies Rewrite it BOM-less: node -e "const f=require('fs'),p='<file>';let s=f.readFileSync(p,'utf8');if(s.charCodeAt(0)===0xFEFF)s=s.slice(1);f.writeFileSync(p,JSON.stringify(JSON.parse(s),null,2))". Never round-trip Claude config through Set-Content -Encoding UTF8; use [System.IO.File]::WriteAllText($p,$json,(New-Object System.Text.UTF8Encoding($false)))
PowerShell 5.1 script dies with "The property cannot be found on this object" $json.NewKey = value throws on 5.1 for keys absent from a ConvertFrom-Json object Add-Member -NotePropertyName ... -Force
A path variable turns into something like MSFT_TaskSettings3 PowerShell variables are case-insensitive$settings silently clobbers $Settings Rename one
Native .exe output appears as red NativeCommandError PowerShell wraps a native program's stderr; the program did not fail Check the exit code, not the color

Cost and footprint

codebase-memory-mcp and Serena are free and fully local. Only claude-mem bills per observation — with the proxy that draws on your Ollama Cloud balance.

Disk Memory
codebase-memory-mcp 282 MB binary + graph cache (~450 MB for 19 repos / 111k nodes) one daemon
Serena small ~1.6 GB with language servers across several open sessions
claude-mem SQLite + Chroma, grows with use worker + embedding stack

Privacy

The graph and Serena are entirely local. claude-mem does send conversation content to a model — the proxy scans message bodies and replaces credentials with [SECRET:{type}] before forwarding, logging [redacted: ...] when it fires. Keep CMP_REDACT on.

License

MIT. The four tools it documents carry their own licenses.

About

Persistent memory + code intelligence for Claude Code: claude-mem, Ollama proxy, codebase-memory-mcp, Serena -- with the global CLAUDE.md that keeps them from competing

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages