AI

Install claude-mem for Claude Code: Persistent Memory, Tested

I installed claude-mem because I was tired of re-explaining the same repository to Claude Code every morning. My first install attempt then sat waiting for a browser login on a headless box. claude-mem is a Claude Code plugin that records what happens in a session, compresses it with a second model and feeds a short summary into the next one. That’s the pitch. But what does it really install, what leaves the machine, and how many tokens does it add to every session you start?

Original content from computingforgeeks.com - post 177197

These are my notes from installing it and using it on a small Python repo. They cover the hooks it registers, the local worker on port 37700 and the three-step MCP search workflow, then private tags, telemetry and a clean uninstall. You’ll also get the token numbers, measured with and without the plugin. I ran all of it in October 2026 on an Ubuntu 26.04.1 LTS VM in my lab called box1, with claude-mem 15.0.2 and Claude Code talking to Anthropic models through OpenRouter, and every prompt and number below comes from that box.

How claude-mem fits into a Claude Code session

claude-mem (Apache-2.0) is three local pieces glued to Claude Code with lifecycle hooks. Every tool call Claude makes fires a hook, which hands the event to a Bun worker listening on 127.0.0.1:37700. The worker then asks an “observer” model to turn the raw event into a short observation that lands in SQLite. The next session in the same project starts with an index of those observations injected into its context, and an MCP server lets Claude pull the full text of any of them on demand.

claude-mem architecture diagram: Claude Code sends tool events to the claude-mem worker on 127.0.0.1:37700, which compresses them through an observer LLM, stores observations in SQLite and returns context and search results, with optional anonymous telemetry

For privacy, the observer call is what matters. The hooks, the worker, the database and the vector index all stay on your machine, but the observer has to read your tool calls to summarise them, so whichever model you point it at sees that content. Telemetry is a separate, smaller stream that I cover further down.

Checking the prerequisites

The installer pulls in Bun and uv by itself, so the list of things you must have up front is short:

  • Node.js 20.12 or newer for npx
  • Claude Code 2.1.139 or newer, logged in
  • A CPU that exposes SSE4.2 (matters on VMs)
  • About 1.2 GB of free disk for Bun, uv and Chroma

Ubuntu 26.04 ships Node 22 in its own archive, so one apt command covers it. Ubuntu 24.04’s archive still carries Node 18, which is too old, so on that release follow the Node.js install guide for Ubuntu and Debian and use the NodeSource repository instead:

sudo apt update
sudo apt install -y nodejs npm sqlite3 jq

I added sqlite3 and jq only so I could look inside the database and settings later; claude-mem itself doesn’t need them. Confirm the Node version before going further:

node --version

Anything from v20.12 up works. My box reported:

v22.22.1

If Claude Code isn’t on the machine yet, the native installer is the quickest route, and the Claude Code install guide for Ubuntu 26.04 covers login and PATH in detail:

curl -fsSL https://claude.ai/install.sh | bash
claude --version

Log in once with claude before installing the plugin, because the hooks run inside your normal Claude Code sessions. My box printed 2.1.310 (Claude Code), comfortably past the minimum.

Error: “this CPU is missing SSE4.2 support, which Bun requires”

My first attempt died right here. The Proxmox VM came from a template with the kvm64 CPU model, which hides SSE4.2 from the guest, and both the Claude Code installer and Bun refuse to run without it:

Setting up Claude Code...
error: this CPU is missing SSE4.2 support, which Bun requires.
  If this is a VM, enable host CPU passthrough (e.g. -cpu host for QEMU/KVM).
Installation was killed before it could finish (exit code 134).

Bare-metal laptops and cloud instances don’t hit this. On Proxmox, switch the VM to host CPU passthrough on the node and power-cycle the guest, because a reboot from inside the VM doesn’t pick up the new CPU model. On a cluster with mixed CPUs, x86-64-v2-AES also includes SSE4.2 and keeps live migration working. Replace 4006 with your VM ID:

qm set 4006 --cpu host
qm shutdown 4006 && qm start 4006

Inside the guest, grep -c sse4_2 /proc/cpuinfo should now print a number above zero; mine printed 2, one per vCPU.

Installing claude-mem without a cmem.ai account

Use npx claude-mem install --provider claude. That flag is the simplest way to skip the cmem.ai sign-in, because it runs the observer on your own Claude plan and skips the cmem.ai sign-in. The plain interactive npx claude-mem install works too on a desktop, but it pre-selects CMEM Pro, the hosted observer that sends your observation content to cmem.ai on a 30-day trial, so read that screen before pressing Enter. Do not use npm install -g claude-mem, because the project’s installation docs say that installs only the SDK, with no hooks and no worker.

npx claude-mem install --provider claude

The run copies the plugin, installs Bun and uv if they’re missing, puts a claude-mem launcher in ~/.local/bin and starts the worker. The end of the run tells you exactly what it decided:

  Skipping claude-mem login: --provider claude runs memory on your own Anthropic plan.
  Disabled cloud sync and saved local Anthropic Max configuration.
  Configured claude-mem to use your logged-in Claude SDK account.
  Spawning worker on port 37700...
  Worker ready at http://localhost:37700 OK

  Installation Complete
  Version:     15.0.2
  Plugin dir:  /home/philip/.claude/plugins/marketplaces/thedotmack
  IDEs:        claude-code
  Account:     Not required (local provider)
  Cloud sync:  OFF (local)
  Auto-memory: left enabled (native Claude Code memory preserved)

Two lines in there are worth reading twice. “Cloud sync: OFF” means nothing is mirrored to cmem.ai. The installer adds that cmem.ai is only contacted if you sign in later, once, with a numbers-only usage summary. “Auto-memory: left enabled” means Claude Code’s own MEMORY.md notes keep running alongside claude-mem, so you have two memory systems feeding the same session. The installer has a --disable-auto-memory flag if you want only one; I left both on.

A marketplace route is documented too, from inside Claude Code, with /plugin marketplace add thedotmack/claude-mem, then /plugin install claude-mem and a restart. I didn’t test that path. The docs warn that a marketplace-only install builds the hook launcher during your first session, so that first session can run without memory, which is one more reason I’d start with npx.

Why –provider openrouter stops at “Waiting for OAuth login in the browser…”

My actual first try was npx claude-mem install --provider openrouter, since I wanted the observer billed to an OpenRouter key and not to a Claude plan. It registered the plugin, printed a cmem.ai/login URL and then sat there:

  Claude Code: plugin registered OK
  Claude Code: leaving native auto-memory enabled unless you explicitly opt in to disabling it.
  Open this URL: https://cmem.ai/login?next=%2Fapi%2Fpro%2Ftrial%2Fclaim%3Fpairing%3D...
  Waiting for OAuth login in the browser…

Was my VM broken? No, this is by design. In the installer source every provider except claude and host counts as needing a cmem.ai account, including your own OpenRouter or Gemini key. If you don’t want an account, press Ctrl+C, install with --provider claude and switch the provider afterwards, which is what the next section does.

Checking the hooks, worker and MCP server

Before trusting it with a real session, I wanted proof that each piece was alive. doctor checks the runtime, the launcher the hooks call and the worker in one go:

npx claude-mem doctor
npx claude-mem status

Every row should carry a check mark. The “Hook launcher” row is the one that breaks most often after a PATH change, and “Chroma processes: 0” on a fresh install is normal because the vector index only starts once there’s something to search:

claude-mem doctor

  ✓ Bun runtime            v1.4.4
  ✓ uv (vector search)     uv 0.13.5 (x86_64-unknown-linux-gnu)
  ✓ Plugin installed       /home/philip/.claude/plugins/cache/thedotmack/claude-mem/15.0.2 (v15.0.2)
  ✓ tree-sitter CLI        /home/philip/.claude/plugins/cache/thedotmack/claude-mem/15.0.2/node_modules/tree-sitter-cli/tree-sitter
  ✓ Hook launcher          /home/philip/.local/bin/claude-mem (protocol 2, --version 19 ms)
  ✓ Marketplace runtime    node_modules and install marker present
  ✓ Marketplace manifest   /home/philip/.claude/plugins/marketplaces/thedotmack/.claude-plugin/marketplace.json
  ✓ Worker daemon          healthy at http://127.0.0.1:37700
  ✓ Claude Code version    2.1.310 (hooks need 2.1.139+)
  ✓ Chroma processes       0 live chroma-mcp tree(s), 0 registered, 0 orphaned

All required checks passed.
Worker is running
  PID: 2104
  Port: 37700
  Version: 15.0.2

The worker port is not always 37700: the default is 37700 plus your user ID modulo 100, so it’s 37700 for UID 1000 and 37701 for UID 1001. Read yours from the settings file, then check that Claude Code lists the plugin and that only one process is listening, on loopback:

CMEM_PORT=$(jq -r .CLAUDE_MEM_WORKER_PORT ~/.claude-mem/settings.json)
claude plugin list
ss -ltnp | grep "$CMEM_PORT"

Mine showed claude-mem@thedotmack enabled at user scope, and the worker bound to 127.0.0.1 only:

  ❯ claude-mem@thedotmack
    Version: 15.0.2
    Scope: user
    Status: ✔ enabled

LISTEN 0      512        127.0.0.1:37700      0.0.0.0:*    users:(("bun",pid=2104,fd=11))

Loopback is the worker’s only protection. The docs say plainly that it has no request authentication, so never forward port 37700 off the machine with ssh -L or a tunnel, because GET /api/settings would hand out your provider API key. The plugin’s hooks.json wires these Claude Code events, almost all through the same claude-mem launcher. A Setup hook and a launcher self-check run under bash and node instead:

Hook eventCommand it runsWhat it does
SessionStartclaude-mem start, hook claude-code contextStarts the worker, injects the memory index
UserPromptSubmithook claude-code session-initRecords your prompt (minus private tags)
PreToolUse (Read, Bash, Grep, Glob, memory tools)hook claude-code file-contextAdds past observations about the files involved
PostToolUse, PostToolUseFailurehook claude-code observationSends each tool call to the observer
Stophook claude-code summarizeWrites a session summary
SessionEndhook claude-code session-endCloses the session record

If you already run your own hooks, they coexist with these. The Claude Code hooks guide explains how plugin hooks and settings hooks are merged.

Switching the observer to OpenRouter

With --provider claude, the observer is configured to run on your logged-in Claude subscription with Haiku as the default model, so memory work eats into the same usage limits as your coding. That comes from the settings file, though. My lab Claude Code ran through an OpenRouter key, so I never ran the observer on a subscription. If you’d rather pay for it separately, the provider can be changed in ~/.claude-mem/settings.json after install, and that edit doesn’t trigger the cmem.ai login. Pick the route by what you want the observer to cost and who should see your tool output:

  • Claude plan (claude): no extra key, shares your plan limits
  • OpenRouter or Gemini key: off-plan, billed or rate-limited by that provider
  • CMEM Pro: off-plan, content goes to cmem.ai, 30-day trial then paid

I went with OpenRouter. Read your key into a variable first (paste it and press Enter) so it never lands in your shell history:

read -rs OPENROUTER_KEY

Then set the provider, key and model in one jq pass, keep the file private and restart the worker. I chose anthropic/claude-haiku-5.5 because it’s cheap and reliable; the claude-mem default for OpenRouter is a free model, which costs nothing but can hit rate limits:

tmp=$(mktemp)
jq --arg k "$OPENROUTER_KEY" '.CLAUDE_MEM_PROVIDER="openrouter" | .CLAUDE_MEM_OPENROUTER_API_KEY=$k | .CLAUDE_MEM_OPENROUTER_MODEL="anthropic/claude-haiku-5.5"' ~/.claude-mem/settings.json > "$tmp" && mv "$tmp" ~/.claude-mem/settings.json
chmod 600 ~/.claude-mem/settings.json
jq -r '.CLAUDE_MEM_PROVIDER, .CLAUDE_MEM_OPENROUTER_MODEL' ~/.claude-mem/settings.json
unset OPENROUTER_KEY
npx claude-mem restart

Both jq lines should echo your new provider and model, followed by a verified worker restart:

openrouter
anthropic/claude-haiku-5.5
Worker restart verified (pid: 2104, version: 15.0.2)

No reinstall is needed, and the change applies from the next observation. To go back to your Claude plan later, set CLAUDE_MEM_PROVIDER to claude the same way and restart the worker again.

Watching one session turn into observations

For a test I made a tiny repo, ~/projects/ratelimit, with a token bucket that never capped its refill, so python3 test_bucket.py failed with AssertionError: 25. Then I asked Claude Code to fix it in one headless run; an interactive claude session in the same folder gets captured the same way:

cd ~/projects/ratelimit
claude -p "Run python3 test_bucket.py. It fails. Find why, fix bucket.py so the bucket never holds more than capacity tokens, rerun the test until it prints ok, then commit the fix with a short message." --output-format json --dangerously-skip-permissions

Use --dangerously-skip-permissions only in a throwaway VM like mine; on your own machine just answer the permission prompts. The session took 12 seconds and five turns, clamped the refill with min(self.capacity, ...) and committed it as 670efa0. When I checked a few minutes later, the worker’s queue was empty and the database held four observations:

CMEM_PORT=$(jq -r .CLAUDE_MEM_WORKER_PORT ~/.claude-mem/settings.json)
sqlite3 -header -column ~/.claude-mem/claude-mem.db "select id, type, title from observations order by id"
curl -s "localhost:${CMEM_PORT}/api/context/inject?project=ratelimit"

One query shows what the observer wrote, and the curl call shows exactly what the next session will be handed at startup.

claude-mem observations in SQLite after one Claude Code session, with the injected context stats line showing 4 observations and 96 percent savings

I liked these titles. Each one is a full sentence a human could act on, naming what failed, what changed, what proved it and which commit holds it. The project name came from the git repository’s folder, so every subfolder of ratelimit shares this memory. The 96% savings figure is claude-mem’s own estimate, 1,093 tokens to read these four observations against 26,447 tokens of original work. It’s not something I measured, which is why the next section exists.

Measuring what persistent memory costs in tokens

Memory isn’t free: whatever is injected at session start is paid for on every session, whether you need it or not. To measure it I ran the same trivial prompt, “Reply with the single word ready”, three times and read the token counts Claude Code reports in its JSON output:

claude -p "Reply with the single word ready" --output-format json | jq '.usage | {input_tokens, cache_creation_input_tokens, cache_read_input_tokens, output_tokens}'

Total input is the sum of the three input fields. Prompt caching moves tokens between “creation” and “read” from run to run, so I compare totals rather than cost:

Run (Claude Sonnet via OpenRouter)Total input tokensCost (USD)
“ready”, no claude-mem28,4890.034
“ready”, claude-mem, empty memory32,7150.044
“ready”, claude-mem, 4 observations32,8430.025
Bug fix session that created the memory (5 turns)134,1190.056
“Without reading any files, what was wrong…”, answered from memory32,8640.027
Explicit search, timeline, get_observations (5 turns)174,7520.074

Most of the overhead has nothing to do with memory. Installing the plugin added 4,226 input tokens to an empty session before a single observation existed. claude-mem registers 27 skills and 19 MCP tools, and it injects a “Work state” instruction block at startup. Claude Code’s tool search was active in my sessions (the explicit search run below had to load the tools with ToolSearch first), so the MCP tool schemas were deferred and the bulk of that cost is most likely the skill descriptions and the startup block. I did not break it down further than that. The four observations themselves added only 128 tokens on top. So the fixed cost is roughly 15% on a small session, and it applies to every claude invocation, including scripted -p calls that will never ask about the past.

Recall is where it paid off. I asked a fresh session “Without reading any files, what was wrong with bucket.py and how was it fixed in the last session?” Told not to touch the files, it still answered correctly in one turn with zero tool calls, straight from the injected index, and it said the answer came from memory and that it hadn’t checked the code. Without claude-mem, that question has no answer unless Claude re-reads the file and the git log. If your sessions are long and keep revisiting the same code, the plugin earns its 4,000 tokens back quickly; if you mostly fire one-off prompts, it’s a tax. The tested tools for reducing Claude Code token usage attack the per-turn side, which is a better first move for that second kind of user.

Searching memory with search, timeline and get_observations

Without any setup from me, plugin:claude-mem:mcp-search showed up as a connected MCP server in the session’s init data. It’s built around a three-step pattern: a cheap index first, context around one hit second and full records last, so Claude never pulls everything at once. I forced all three with a direct prompt:

claude -p "Use the claude-mem search tool to find past work on the token bucket capacity bug, then call timeline on the best result, then get_observations for the IDs you need. Report the observation IDs you used and what they say." --output-format stream-json --verbose --dangerously-skip-permissions

Pulling the tool calls out of the stream showed Claude first loading the deferred tool schemas with ToolSearch, then doing exactly what I asked, with sensible arguments at each step:

{"name":"ToolSearch","input":{"query":"select:mcp__plugin_claude-mem_mcp-search__search,mcp__plugin_claude-mem_mcp-search__timeline,mcp__plugin_claude-mem_mcp-search__get_observations","max_results":3}}
{"name":"mcp__plugin_claude-mem_mcp-search__search","input":{"query":"token bucket capacity bug","project":"ratelimit"}}
{"name":"mcp__plugin_claude-mem_mcp-search__timeline","input":{"anchor":1,"project":"ratelimit"}}
{"name":"mcp__plugin_claude-mem_mcp-search__get_observations","input":{"ids":[1,2,4]}}

Result sizes show why the order matters:

  • search: 1,192 characters, 4 observations plus 5 past prompts
  • timeline: 851 characters around observation 1
  • get_observations: 3,891 characters for three full records

Claude skipped observation 3 on its own, explaining that it only confirmed the passing test. So the expensive step fetched three records, not four. In practice I’d let the injected index do most of the work and only ask for search when the answer is older than the last few sessions, because this run used five turns and 174,752 input tokens against 32,864 for the plain recall question. If you’re new to MCP in Claude Code, the MCP servers setup guide explains how plugin-provided servers differ from ones you add with claude mcp add.

Keeping secrets out with private tags and telemetry off

Anything you wrap in <private> tags is visible to Claude during the session but stripped before it reaches the database. I tested it with a fake password in a prompt:

claude -p "Add a docstring to allow() in bucket.py. <private>Staging DB password is FAKE-PASS-7731, never store this.</private>" --dangerously-skip-permissions

Two minutes later, after the observer had processed the session, I searched both the SQL dump and every file under the data directory:

sqlite3 ~/.claude-mem/claude-mem.db .dump | grep -c FAKE-PASS-7731
grep -rl FAKE-PASS-7731 ~/.claude-mem
sqlite3 ~/.claude-mem/claude-mem.db "select id, substr(prompt_text,1,200) from user_prompts order by id desc limit 3"

The count was 0, grep found no file, and the newest stored prompt (ID 6) had been cut at the tag:

0
6|Add a docstring to allow() in bucket.py.
5|Use the claude-mem search tool to find past work on the token bucket capacity bug, then call timeline on the best result, then get_observations for the IDs you need. Report the observation IDs you use
4|Without reading any files, what was wrong with bucket.py and how was it fixed in the last session?

Private tags protect claude-mem’s storage, not the session. The docs say the tags are stripped at the hook layer, so the observer never receives the tagged text, but the main Claude Code model still reads your whole prompt, and if Claude later repeats the secret in a tool call outside the tags, that call is captured like any other. I only verified the database side, so keep real credentials out of prompts entirely.

Does anything else leave the machine with the claude provider? Telemetry does, and it’s on by default: npx claude-mem telemetry status on my fresh install reported Telemetry: ENABLED, “Decided by: default”. The project documents it as anonymous PostHog events keyed to a random install ID, with no prompt text or paths. Error reports are a separate stream that the docs say sends redacted error messages and stack traces. The documented off switches:

  • npx claude-mem telemetry disable: usage events and error reports both off
  • CLAUDE_MEM_TELEMETRY=0 or DO_NOT_TRACK=1 in the environment
  • CLAUDE_MEM_TELEMETRY_ERRORS=0: error reports off, usage events kept

The variables only work if the hook and worker processes see them, so put them in your shell profile or the env block of Claude Code’s settings rather than one terminal. The interactive installer asks about telemetry once at the end; my non-interactive run never asked, which is why I’d check the status on any work machine and turn it off there.

Counting what the plugin runs on your machine

claude-mem is heavier than “a plugin” suggests, and you should know that before installing it on a small VM. These are the numbers from box1 after six sessions:

ItemMeasured
Bun worker resident memory109 MB
Chroma vector search (Python via uvx) resident memory249 MB
~/.local/bin/claude-mem launcher104 MB binary
~/.bun576 MB
~/.cache/uv (Chroma environment)508 MB
~/.claude-mem data4.2 MB

Data is tiny here, but the runtimes are not. Chroma only started once memory existed, which is why doctor showed zero Chroma processes right after install. Budget about 360 MB of RAM and 1.2 GB of disk on the machine where you run Claude Code.

Removing claude-mem cleanly

Uninstalling stops the worker, removes the plugin, the marketplace entry, the hook launcher and its own settings in Claude Code, then walks through every other editor integration it supports:

npx claude-mem uninstall

Mine finished with exit code 0 and one note that matters:

  Hook launcher removed (/home/philip/.local/bin/claude-mem) OK
  Stray paths removed: 4 OK
  Your data directory at ~/.claude-mem was preserved.
  To remove it manually: rm -rf ~/.claude-mem
  claude-mem has been uninstalled.

After it, claude plugin list reported no plugins, which claude-mem found nothing and nothing listened on 37700. What stayed behind:

  • ~/.claude-mem, including settings.json with the OpenRouter key
  • ~/.bun and ~/.cache/uv, about 1.1 GB together
  • uv and uvx in ~/.local/bin
  • An empty ~/.claude/plugins/cache/thedotmack folder

The first one is the important leftover, because it holds both your memory database and any API key you saved. Delete it if you’re done with claude-mem for good:

rm -rf ~/.claude-mem

Remove Bun and uv only if nothing else on the machine uses them, and check ~/.bashrc afterwards for any Bun PATH lines the installers added:

rm -rf ~/.bun ~/.cache/uv ~/.local/bin/uv ~/.local/bin/uvx ~/.claude/plugins/cache/thedotmack

For the native memory commands that remain once the plugin is gone (/memory, CLAUDE_CODE_DISABLE_AUTO_MEMORY and the rest), the Claude Code cheat sheet has them on one page.

What I still don’t know

A few things didn’t settle in the lab, and I’d rather list them than guess:

  • Session summaries: zero written across six headless sessions
  • The marketplace install path, which I didn’t run
  • How the 4,226-token overhead grows with a larger memory

The summary gap bothers me most. The Stop hook is registered and observations were written within seconds, yet session_summaries stayed at 0, and all my sessions were claude -p runs. My guess is that summaries behave differently in interactive sessions, but I haven’t tested that, so check select count(*) from session_summaries on your own install after a normal day of work. On the overhead, the default injects up to 50 observations, so a busy project’s fixed cost will be larger than the 128 tokens my four observations added; lowering CLAUDE_MEM_CONTEXT_OBSERVATIONS is the lever if it gets heavy. For now I’m keeping claude-mem on one long-running project and off everywhere else.

Keep reading

Ollama Models Cheat Sheet: Best Models by VRAM, Tested (PDF) AI Ollama Models Cheat Sheet: Best Models by VRAM, Tested (PDF) Open Source LLM Comparison Table (2026) AI Open Source LLM Comparison Table (2026) Claude Code Cheat Sheet: Commands, Shortcuts and Flags (PDF) AI Claude Code Cheat Sheet: Commands, Shortcuts and Flags (PDF) Claude Code Plugins: Install, Manage and Pick Official Plugins AI Claude Code Plugins: Install, Manage and Pick Official Plugins GPT-6.1 Sol, Terra and Luna: Benchmarks, Pricing and Tests AI GPT-6.1 Sol, Terra and Luna: Benchmarks, Pricing and Tests Run a Local LLM with llama.cpp on Linux AI Run a Local LLM with llama.cpp on Linux

Leave a Comment

Press ESC to close