It started as a couple therapy session, where I asked Claude why it didn’t pay enough attention to what I was saying, and kept forgetting things I was certain we had already discussed. As if nothing was wrong, Claude said that it had no memory of such conversation and preferences. “But I remember you writing stuff in your memory,” I protested, to which Claude, after some back and forth and a good deal of hallucinations, replied: “Oh, that! I can explain. It’s not what you think.”

As it turns out, Claude does remember things. Just not the way I expected (hence the “it’s not what you think” part).

The problem: memory lives in silos

Since version 2.1.32, Claude Code has a built-in memory system. During a session, when something worth remembering comes up (a preference, a correction, a working style note), Claude writes it to a memory file specific to that project. Next time you open a session in the same directory, Claude reads those files and picks up where you left off.

The key word there is “specific to that project.” You see, Claude projects are like Vegas: what Claude learns in your WordPress plugin project stays in your WordPress plugin project. If you’re also working on a Node.js API and you’ve spent three sessions getting Claude to understand how you structure your error handling, none of that travels. Open a new project, and you’re back to square one.

So the asymmetry looks something like this: Claude knows that in your plugin project you prefer descriptive variable names and avoid abbreviations (because you told it once and it wrote that down). But in your API project, it has no idea. So you tell it again, and it writes it down there too. Yet, two weeks later you start a third project and the cycle repeats.

In reality, it seems like a typical case of “it’s not a bug; it’s a feature.” It’s a design decision, and probably a reasonable one: project-specific memory keeps context focused and avoids polluting one project’s session with irrelevant details from another. But it means that cross-project preferences, the things you care about regardless of what you’re building, don’t accumulate.

The diagnosis: where does memory actually live?

Claude Code stores project memory as plain files in a folder that maps to your current working directory. The path is something like ~/.claude/projects/<encoded-path>/memory/, where the encoded path is your cwd with slashes replaced by dashes. Each memory entry is its own Markdown file with some frontmatter (type, name, description), and there’s a MEMORY.md index that lists them all.

flowchart TD
    A[Something worth remembering<br/>comes up in a session]
    B[Claude writes a memory file<br/>to project memory folder<br/>~/.claude/projects/encoded-cwd/memory/]
    C[Session ends]
    D[New session opens<br/>in the same project]
    E[Claude reads memory files<br/>from project folder]
    F[Claude applies preferences<br/>for this session]
    G[New session opens<br/>in a different project]
    H[Different project memory folder<br/>no memory files from other projects]
    I[Claude starts fresh<br/>no knowledge of other project preferences]

    A --> B --> C
    D --> E --> F
    G --> H --> I

    C -.->|same project| D
    C -.->|different project| G

This is actually a well-designed system. The files are readable, editable, and easy to back up. The problem is purely one of scope: each project folder is its own island.

The natural question is: can we build a bridge?

The approach

The proposal, once you see the problem clearly, is fairly straightforward. There are two kinds of things Claude might learn about you. Things that are specific to a project (“the staging environment for this plugin is at this URL”, “the lead developer prefers atomic commits”) and things that reflect who you are regardless of which project you’re working on (“you prefer explicit over implicit”, “you don’t need basic concepts explained”, “you want one-line summaries before code blocks”).

The second category is what we want to preserve globally.

So the plan was: once in a while, review all per-project memory folders, extract the globally-relevant entries, and store them somewhere central, outside of Claude’s own project structure, version-controlled and easy to back up. Then, at the start of every new session, inject those global entries into the current project’s memory folder so Claude picks them up naturally, without any change to how the memory system itself works.

The “without any change” part matters. The goal wasn’t to hack around Claude’s memory mechanism but to work with it. Global memories look exactly like local ones because they get copied into the same folder structure. Claude doesn’t know the difference, and it doesn’t need to.

The interesting design decisions

Once you decide to do this, a few questions come up that are worth thinking through.

The first is what qualifies as global. Preferences about your working style, communication patterns, things you’ve corrected Claude on more than once: those travel. Project-specific context, references to external systems, anything that starts with “in this project”: those stay local. When in doubt, err toward exclusion. Global memory should be signal, not noise.

The second is file structure. Claude’s own memory system uses individual files, one per entry, with a MEMORY.md index. Keeping the same structure for the global store means global entries behave identically to local ones once injected. A single big file would have been simpler to manage but would have created a mismatch when the files landed in a project’s memory folder. The symmetry is worth a little extra complexity.

The third, and most interesting, is conflict detection. Suppose you have a global memory entry that says “always summarise before writing code”. Now you’ve been working on a project where, for good reason, you decided the opposite. What happens when global memory gets injected into that project? Without conflict handling, you’d end up with contradictory instructions in the same memory folder.

So before writing anything globally, the system compares proposed global entries against all project memories and looks for contradictions. Same filename, different rule; different filename, opposing instruction; or the subtle one: different filename, same underlying rule, which creates redundancy rather than conflict. Each case gets surfaced for a decision before anything is written.

The fourth is local-wins priority. When injecting global memories into a project, if a file with the same name already exists locally, it is never overwritten. The project-specific version takes precedence. This keeps the injection idempotent and safe.

How it works in practice

There are two moving parts.

The first is a skill you invoke on demand. It scans all per-project memory folders, classifies each entry as global or project-specific, deduplicates entries that appear across multiple projects with slightly different wording, checks for conflicts (presenting each one for a decision before writing anything), and rewrites the global memory store from the current findings. You run this occasionally, when you feel like global preferences have shifted or when you’ve had enough sessions across different projects to make it worth reviewing.

The second is a SessionStart hook. Every time you open a Claude Code session, the hook reads the global memory store and copies any files not already present in the current project’s memory folder. Claude then loads them as part of its normal startup, treating them exactly like local memory. No special handling, no separate injection mechanism, and no difference in how Claude experiences them.

The global memory files live outside of Claude’s setup directory, in a folder you control and version-control yourself. That means they travel with you to a new machine, survive a Claude Code reinstall, and are easy to back up.

flowchart TD
    A[Session ends<br/>Claude writes memories<br/>to project memory folder]
    B[/update-memory skill<br/>invoked on demand/]
    C[Scan all per-project<br/>memory folders]
    D[Classify each entry:<br/>global vs project-specific]
    E[Deduplicate &<br/>consolidate]
    F{Conflicts?}
    G[Present conflicts<br/>to user for decision]
    H[Rewrite global<br/>memory store<br/>in dotfiles]
    I[New session opens]
    J[SessionStart hook fires]
    K[Copy global memory files<br/>into project memory folder<br/>local wins — never overwrite]
    L[Claude loads memory<br/>as normal startup]

    A -->|over time, across projects| B
    B --> C
    C --> D
    D --> E
    E --> F
    F -->|yes| G
    G -->|resolved| H
    F -->|no| H
    H -->|committed to dotfiles<br/>backed up, portable| H

    I --> J
    J --> K
    K --> L

    H -.->|global memory available<br/>for next session| J

The result

The practical change is this: preferences now accumulate across projects. Something Claude learned about your working style three months ago in a completely different codebase shows up naturally in a brand new project from day one.

The therapy sessions still happen, of course. But at least now Claude remembers what we talked about last week.

Leave a Reply

Your email address will not be published. Required fields are marked *