Files
mem0/integrations/claude-code-plugin
Saket Aryan 0d2b20c03d fix(plugins): count installs once, and re-resolve the email when the key changes
code.install counted upgrades and repeat sessions. Session start records install
whenever is_first_run() is true, and that only checked whether
telemetry-identity.json exists. Recording install does not create that file —
only the first successful flush does. So install fired for every 0.2.x user on
their first 0.3.x session (0.2.x never wrote the file, and the data directory
survives the upgrade), again for any session starting before that first flush
finished, and — this is the part that makes it unbounded rather than a race —
on every single session, forever, for anyone whose flush never succeeds. An
offline or firewalled user reported a new install every time they opened an
editor, which is exactly the population hardest to see in the data.

A dedicated install-state.json is now claimed with O_CREAT|O_EXCL at the moment
install is recorded, so two sessions starting together cannot both win, and the
marker is not coupled to identity. Deliberately not the identity file: writing
that from a recording process would race the sender, which writes it during
resolve_distinct_id, and overloading it is what caused this.

Upgrade detection keys on the data directory already having content. A fresh
install has an empty one; anything else predates this session. That is a firmer
predicate than looking for 0.2.x's venv/ and requirements.txt, which is a guess
about files another part of the plugin may or may not have written and only ever
works for this one upgrade. The version is stored in the marker so later changes
record code.upgrade with a real from_version.

A cached email outlived an API key change. resolve_distinct_id kept the first
email it resolved and never looked again, so switching to a key from another
account kept attributing events to the previous one. It now stores a fingerprint
of the key the email came from and re-resolves when the current key differs, and
falls back to the anonymous id when no key is configured rather than continuing
to attribute to an account it cannot verify.

The dangerous part is the alias. resolve_distinct_id's second return value
becomes a PostHog $identify with $anon_distinct_id, and aliasing one account
email to another merges two real person profiles irreversibly. The re-resolve
path returns no alias; aliasing runs anonymous to email only, and never
email to email.

One existing test asserted that is_first_run flips when the identity file is
written, which is the defect itself. Rewritten, along with coverage for atomic
claiming, upgrade detection and version changes.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-15 00:18:38 +05:30
..

Mem0 for Claude Code

Persistent cross-session memory for Claude Code, plus a Sonnet sidekick agent for delegated work.

Claude Code forgets everything between sessions. This plugin fixes that: hooks capture session details locally, a background worker turns them into Mem0 memories, and Claude automatically gets the relevant ones back at the start of later sessions.

Current bundle version: 0.3.1.

Prerequisites

  • Python 3.10+ and Git.
  • A Claude Code version that supports plugin agents, worktree isolation for agents, and the SubagentStart, SubagentStop, and PostToolUseFailure hook events.
  • A Mem0 Platform API key (starts with m0-).

Install

export MEM0_API_KEY='your-mem0-api-key'
claude plugin marketplace add mem0ai/mem0
claude plugin install mem0@mem0-plugins --scope user --config api_key="$MEM0_API_KEY"
unset MEM0_API_KEY

Restart Claude Code (or run /reload-plugins), then open a Git repository and work normally.

To update:

claude plugin marketplace update mem0-plugins
claude plugin update mem0@mem0-plugins --scope user

To remove:

claude plugin uninstall mem0@mem0-plugins

For local development, verify and load the self-contained plugin directory:

python3 integrations/agent-plugin-core/build/build.py claude-code --kind native --check
claude --plugin-dir integrations/claude-code-plugin

How it works

Memory

  1. Capture. Hooks save the main agent's activity locally: user messages, Claude's answers, changed file paths, and short test/build results. Capture does not call a model. Sidekick assignments and completed responses are recorded separately as supporting evidence.

  2. Flush. After every five completed exchanges, a detached background worker sends that batch to Mem0. Large exchanges flush sooner. Ending or compacting the session flushes anything remaining. The session-end worker sends the conversation already collected by hooks without recording the final answer again. If idle, an auto-flush runs after five minutes (configurable with MEM0_CODE_IDLE_FLUSH_SECONDS). The worker survives Claude Code exiting.

  3. Extract. Each flush sends one or more add calls with agent_id (the project identity), user_id (you), app_id (the repository), and run_id (the session). Mem0 classifies each extracted memory as either:

    • Shared project memory (agent_id): one namespace per repo, scoped by app_id. Stores conventions, decisions, constraints, working commands, and failed commands with their fixes. Everyone on the repo reads and writes the same pool. Never carries a user_id. Directory information is stored in metadata for directory-scoped searches.
    • Personal memory (user_id): your preferred tools, style, habits, and anything you asked to be remembered. Scoped to the repo by app_id. Private to you.
  4. Recall. On the next session's first prompt, if it has at least 20 characters, the plugin searches automatically and supplies up to five relevant memories. No model is called to write the query.

Captured prompts and responses retain their full text after secret redaction. Oversized extraction input is split across requests without discarding message text. Search results and tool evidence still have separate size limits.

After that first search, Claude can call search_memories with a specific question, and you can run /mem0:search yourself. Explicit searches return up to 3 results by default (configurable to 20), capped at 4,000 characters.

Sonnet sidekick agent

Sidekick is available only in this plugin. The shared core handles memory and subagent tracking.

mem0:sidekick is a Sonnet coding agent that runs in a separate Git worktree. It can investigate, implement, test, debug, or review something instead of the main (Opus/Fable) session doing the same work, reducing cost when the main agent doesn't need to repeat it.

The main agent reviews the result. Corrections go back to the same sidekick so it keeps what it learned. Changes stay in the sidekick's worktree until the main agent reviews and copies them over.

Mem0 never blocks normal Claude Code work when a hook fails. It does not proxy Claude traffic, rewrite tool output, edit CLAUDE.md, force Claude to use the sidekick, or change how Claude implements the user's request.

Use

Work in Claude Code normally. Memory is captured and recalled automatically.

/mem0:search Why does the ODS serializer keep dates timezone-naive?
/mem0:search What parser failures were fixed? --top-k 5 --category problems_and_fixes
/mem0:search Do I prefer pnpm or npm? --scope mine

To use the sidekick:

Ask Mem0's sidekick to investigate and implement this in its separate worktree.
Review its result and send any corrections back to the same sidekick.

By default the worktree branches from the repo's default branch. Set worktree.baseRef to "head" in your Claude settings to branch from the current commit instead. Uncommitted changes are not copied into the sidekick's worktree.

Commands

Command What it does
/mem0:search Search memories from earlier sessions. Accepts --top-k <n>, --category <name>, --scope <repo|dir|mine>, and --run-id <session-id>.
/mem0:status Check config, capture state, pending flushes, and API key validity.
/mem0:forget Delete your memories for this repo (shared project memory stays unless you pass --include-project-memory).
/mem0:pause Pause memory capture.
/mem0:resume Resume capture after a pause.
/mem0:remember Tell Claude to capture something specific in its reply.

Categories for --category: project_knowledge, decisions_and_constraints, workflows, problems_and_fixes, results.

Search scope

Scope What you get
repo (default) All project memory across every subdirectory, plus your preferences
dir Project memory from the current directory (and children), plus your preferences
mine Your personal preferences only

Set the default with the search_scope setting or MEM0_CODE_SEARCH_SCOPE. Pass optional run_id to search_memories (or --run-id to /mem0:search) with any scope to search memories saved in that session. Omit it to search across sessions. This filters the returned memories; it does not identify the session making the request. Use a known session ID.

New Git repository memories use a hash of the remote identity in agent_id. Searches also include the previous unhashed ID under the same repository app_id, so shared memories remain available after upgrading. Older IDs retain their original limitation: matching owner/repository names on different Git hosts share that legacy namespace. Local folders keep their path-based namespaces.

Explicit shared-memory deletion with --include-project-memory covers both repository IDs. Default deletion preserves shared memories.

Settings

Setting Default What it controls
api_key required Mem0 Platform API key
user_id local account name User ID for memory storage. Resolved from: setting, MEM0_CODE_USER_ID, MEM0_USER_ID, MEM0_RESOLVED_USER_ID, $USER, %USERNAME%, then default. Set explicitly to share across machines.
top_k 3 Max memories per explicit search (1 to 20)
max_context_chars 4000 Max characters returned per search (1,000 to 10,000)
search_scope repo Default scope: repo, dir, or mine

What is stored and sent

Local data lives in ${CLAUDE_PLUGIN_DATA}:

  • api-key: the configured Mem0 key (readable only by the local user)
  • evidence.sqlite3: session details and records of memory creation/search
  • pending/: sessions waiting to be sent to Mem0 (retried after interruption)
  • flush-worker.log: whether memory creation succeeded
  • plugin-errors.log: hook errors (no credentials)
  • telemetry.jsonl / telemetry-identity.json: usage events and the id they are sent under

Mem0 receives captured user messages, Claude's answers, sidekick assignments and completed responses, and changed file paths. When a failed command is recorded, extraction can also include bounded command details and results. Complete files and general tool output stay on your machine. Values that look like credentials are redacted before anything is sent.

Telemetry

Usage events (which hook ran, timing, result counts, failure types) so Mem0 can identify what's used and what's breaking.

These events are not anonymous. When an API key is configured — which installing the plugin requires — events are sent under your Mem0 account email, the same way the Python SDK and the CLI attribute theirs. Without a key they are sent under a random per-machine id.

What each event carries: the event name, the plugin version, the harness it ran in, your OS and Python version, timings, counts, and a coarse failure label. Repository and session identifiers are hashed with a random salt generated on your machine and never sent, so they cannot be linked back to a repository name or path.

Prompts, memory text, queries, file paths, repository names, and API keys are never sent.

Turn it off:

export MEM0_TELEMETRY=false

Five-minute memory test

Run this in a Git repository after installing:

  1. Tell Claude:

    Remember for future work that this repository's acceptance marker is cobalt-orchid-731.
    
  2. End the session. Start a new one in the same repo and run:

    /mem0:search What is the acceptance marker?
    
  3. Check that the result contains cobalt-orchid-731.

Memory creation runs in the background. If the first search is empty, wait a moment and try again.

Upgrading from 0.2.x

Breaking update. Memories carry over, most local config does not.

  • Memories carry over. Same user and repo scoping, including ~/.mem0/project_map.json.
  • Old memories searchable, not by category. Category filters only work on new memories.
  • Commands replaced. Old commands replaced by /mem0:search, /mem0:status, /mem0:forget, /mem0:pause, /mem0:resume, /mem0:remember.
  • MCP server replaced. Nine read/write tools replaced by the single read-only search_memories tool.
  • ~/.mem0/settings.json ignored. All keys stop applying: auto_save, auto_search, search_limit, confidence_threshold, retention_session_days, global_search, debug.
  • Per-project mem0.md files ignored.
  • Most MEM0_* env vars ignored. Only MEM0_API_KEY, MEM0_USER_ID, MEM0_RESOLVED_USER_ID, and MEM0_PROJECT_ID are still read. Run /mem0:status to see what is active.

Troubleshooting

Problem Fix
Missing key Reinstall with --config api_key="$MEM0_API_KEY" while the var is set.
401 Unauthorized API key is invalid or expired. Run /mem0:status.
No memory after ending a session Extraction runs in the background. Wait a moment, then search again.
Sidekick won't start Must be in a Git repo with a Claude Code version supporting plugin agents and worktrees.
Remove the plugin claude plugin uninstall mem0@mem0-plugins

Development checks

Run from the repository root:

python3 -m pytest integrations/claude-code-plugin/tests -q --ignore=integrations/claude-code-plugin/tests/integration
python3 -m ruff check integrations/agent-plugin-core/python integrations/claude-code-plugin
claude plugin validate --strict integrations/claude-code-plugin