Post #4 in the Claude Code Toolkit series. Earlier posts: nf-agents (spawning teams), nf-dream (consolidating the memory folder), nf-git-workflow (per-repo authorized git modes). This post is about the skill that sits at the start of the memory pipeline: the one that decides where memory is written in the first place.
autoMemoryDirectory is a per-project setting in .claude/settings.local.json. Every new repo starts without it. Want two projects to share a folder so cross-project facts stop being duplicated? Manual edit in each repo. Want to migrate an old per-project memory folder into a shared one? Manual copy, manual dedupe, manual prayer that no file was overwritten. The friction is small on the first project and not-so-small the moment you have six.
nf-memory is the skill that makes that flow take five minutes instead of an afternoon.
The friction this skill solves
autoMemoryDirectory itself is great. Point project-a and project-b at ~/.claude/memory/<scope>/, both sessions read and write the same entries, cross-project facts stop being duplicated. The setting is the unlock.
What is friction is everything around the setting:
- The folder has to exist before Claude Code loads the project. Set the key to a path that does not exist and the next session quietly creates an empty default at the project-local location instead.
- The setting goes in
.claude/settings.local.json(gitignored, personal), not.claude/settings.json(committed). Wrong file leaks personal paths into the team’s repo. - The local settings file may already exist with unrelated keys (permission allowlists, env vars, hooks). You cannot blindly
Writeit; you have to merge. - If you point the path at a directory that contains the current project, Claude Code recursively scans the project as memory on every boot. Boot times explode and nothing in the UI tells you why.
- You usually have existing memory to migrate from somewhere: the previous shared folder, the default
<cwd>/.claude/memory/, or both. Some files are project-specific, some are generic. A blanketcp -rdoes the wrong thing on every conflict.
By the time I had six projects routing memory through ~/.claude/memory/personal/ and another four through ~/.claude/memory/<client-org>/, doing this by hand for each new repo cost ten minutes per project plus the occasional mistake. The skill fixes the workflow once.
What the skill is
A SKILL.md that drives Claude through a small state machine:
- Resolve the target folder, either from an explicit argument (
/nf-memory personal) or via an interactive picker. - Validate the input (bare name vs path-like) and detect obvious typos against existing folder names.
- Refuse the configuration if the target equals or contains the current project (recursive-memory safeguard).
- Read
.claude/settings.local.jsonif it exists, merge theautoMemoryDirectorykey without touching other keys, then write back. Create the file fresh only if it does not yet exist. - Detect candidate migration sources (the previous shared folder if any, plus the project-local
.claude/memory/if any) and offer to migrate with per-file conflict handling. - Add
.claude/settings.local.jsonto.gitignoreif missing.
Every decision point runs through AskUserQuestion: pick a folder, confirm apply, pick sources to migrate, resolve filename or topic conflicts. No free-form prompt I have to remember a syntax for six months later.
The pair with nf-dream
nf-memory and nf-dream are the two halves of a memory pipeline.
nf-memory decides where memory lives. It runs once per project. After it runs, every memory write from the host project lands in the shared folder.
nf-dream keeps that folder healthy. It runs periodically (in my setup, every two or three weeks per project): classify each file as Now / Next / Done / Future / Reference / Stale, rewrite MEMORY.md grouped by bucket, archive aged session-end states, write a one-page HANDOFF-<slug>.md that next-week-me reads in 30 seconds. Snapshot first, never rm.
The pair is intentional. nf-memory is the “set up” step (low frequency, write-heavy, requires care). nf-dream is the “tidy up” step (high frequency, mostly read, snapshot-protected). One configures the folder, the other consolidates it. Run nf-memory once, nf-dream forever.
Skip nf-memory and you lose cross-project sharing. Skip nf-dream and the folder becomes a 200-file swamp by month three.
A first run, end to end
You just cloned a new repo for an existing client. You already have a memory folder at ~/.claude/memory/<client-org>/ from two earlier projects with the same client. You want the new project to share it.
$ cd ~/projects/<client-org>/<new-project>
$ /nf-memory
Reading .claude/settings.local.json...
Current autoMemoryDirectory: not set
Existing memory folders (under ~/.claude/memory/):
<client-org>/ (37 files, last modified 2 hours ago)
<other-org>/ (12 files, last modified 4 days ago)
personal/ (8 files, last modified 3 weeks ago)
> Pick a memory folder.
1. <client-org> (recommended) 37 files
2. <other-org> 12 files
3. personal 8 files
4. Other (type a custom name or path)
Selection: 1
Preview:
Target path: ~/.claude/memory/<client-org>/
Status: existing, 37 .md files
Will update: .claude/settings.local.json (key: autoMemoryDirectory)
Apply this configuration? Yes / No
> Yes
Wrote .claude/settings.local.json
Verified .gitignore contains .claude/settings.local.json
Migration:
Source detected: <cwd>/.claude/memory/ (project-local, 3 files)
Migrate 3 files into <client-org>/ (source will not be modified)? Yes / No
> Yes
Per-file migration:
api-conventions.md no conflict, copied
decision-log-2026-04.md no conflict, copied
infra-overview.md filename conflict, consolidated with existing
Report:
Copied as-is: 2 files
Consolidated: 1 file
Sources processed: <cwd>/.claude/memory/ (untouched)
MEMORY.md merged: 3 new entries
The new project now shares the <client-org> folder. Open one of the older sister projects and it sees the new entries too. The source folder (<cwd>/.claude/memory/) stays intact as a backup; delete it by hand later if you want.
The rest walks through the four design decisions inside the skill that took the longest to get right, with the incident behind each.
Decision 1: Read before write on settings.local.json
The single biggest source of damage when modifying a settings file is blindly Write-ing an existing file. Write replaces the whole content. If the file had ten other keys, they are gone.
The skill always checks first:
test -f .claude/settings.local.json
Three branches follow:
- File does not exist. Safe to
Writea minimal JSON object:{ "autoMemoryDirectory": "<path>" }. Ensure.claude/exists first withmkdir -p. - File exists with valid JSON. Use
Readto load it, thenEditto replace just theautoMemoryDirectoryline (or insert it if absent), preserving every other key, the trailing-comma rules, and the indentation. NeverWritean existing file. - File exists but is empty or malformed JSON. Fall back to
Writewith the minimal object, but print a warning in chat first so the user knows the file was clobbered (better than failing silently when the file was a stub anyway).
The incident: an early version of the skill ran Write unconditionally. The user it ran for had a permissions.allow block with about twenty hand-curated entries. The Write wiped them. Took ten minutes to recover from git reflog and re-derive the list from ~/.claude/projects/.../<session-id>.jsonl transcripts. Fix shipped the same day.
The general lesson applies to any tool touching a user state file: settings, history, credentials, package locks. Read first, merge, then Write (or Edit). The five extra lines of code are non-negotiable.
Decision 2: AskUserQuestion picker over an assumed default
The early prototype had no picker. It auto-defaulted the target folder to ~/.claude/memory/<project-basename>/, with a flag to override. The reasoning was “fewer prompts, faster flow.”
That was the wrong default. The whole point of autoMemoryDirectory is sharing memory across projects. Auto-defaulting to a per-project folder gives you exactly the project-local layout you would have without the setting at all. The skill became a no-op for its main use case.
The fix was an AskUserQuestion picker that lists existing shared folders first, sorted by recently modified, with a “type a custom name or path” option via the auto-provided “Other”. The user picks in 2 seconds. The picker is the primary affordance, not a fallback.
Side benefit: the list of folders is its own gentle prompt. If you forgot you had a <client-org> folder from three months ago and you start a new project for that same client, the picker reminds you so you do not accidentally create a new isolated one.
The picker also surfaces an optional cwd-mapped suggestion. If ~/.claude/references/nf-memory-mappings.md exists (a small file with cwd-prefix => folder-name mappings), the skill finds the longest matching prefix and tags it (recommended). This is the closest the skill comes to an auto-default, and it is opt-in.
Wider lesson: for any setting that is a personal preference, an AskUserQuestion picker beats a magic default. Personal preferences are the wrong thing to encode in code.
Decision 3: Smart merge on migration, not blind copy
When the new project has memory at <cwd>/.claude/memory/ (the old default), and the new target is ~/.claude/memory/<scope>/, the skill offers to migrate. The naive implementation is cp -r. It is wrong.
The conflicts you hit immediately:
- Both folders have a
MEMORY.md.cpoverwrites the target’s index with the source’s. - Both folders have a file with the same name (
project_session_end_state_<date>.md,api-conventions.md).cpoverwrites whichever the user worked on most recently. - Different filenames describe the same topic.
cpcreates two files about one decision, and the next session has no idea which to trust.
The skill walks per-file:
- No conflict (target lacks the filename, and target’s
MEMORY.mdindex does not describe the topic) → copy mechanically. - Filename conflict →
Readboth, write a consolidated version into target. Prefer newer / more specific entries. - Topic overlap (different filenames, same subject, detected via target’s
MEMORY.mddescriptions) →Readboth, write consolidated. Pick whichever filename is clearer. - Contradiction (sources disagree on a fact) → do not silently pick.
AskUserQuestionwith both versions presented, plus a “Skip this file” option.
MEMORY.md itself is merged last: read each source’s index, read the target’s, dedupe entries by file path, write the union back.
The critical rule: sources are read-only. The skill never modifies, moves, or deletes a source file. If migration breaks something, the original folder is still there. The user deletes it by hand later, after verifying the migration worked. That paranoia is the feature.
The incident: an earlier version moved source files into archive/ after copying. Sounds tidy. The first user who hit a migration mid-laptop-reboot ended up with half the files moved, half still in source, no easy way to retry. Switched to read-only sources the next day. Migration is idempotent now: re-run it and it no-ops on already-copied files.
Decision 4: Refuse to point at the same folder twice (recursive-memory safeguard)
The foot-gun: you set autoMemoryDirectory to ~/.claude/memory/<scope>/. You then cd into ~/.claude/memory/<scope>/ to organize files. You boot Claude Code from inside that folder.
Claude Code reads .claude/settings.local.json (which may exist here if you have ever run the skill from this directory). The setting points at . itself. The session loads every file as memory. Every memory write creates an entry about memory. Boot times explode. Memory traffic becomes recursive.
The skill detects this before applying:
If the resolved target path equals the current cwd
OR the resolved target path is a parent of the cwd
→ refuse with a clear error,
ask via AskUserQuestion for a different path or cancel.
Two realpath resolves and a substring compare. Cheap. The benefit is enormous: the failure mode is invisible (no error, just slow sessions), so catching it at configuration time matters more than catching it at runtime would.
The incident: I once spent twenty minutes debugging why a session loaded slowly in a directory I was using to manually clean memory files. The cwd was inside ~/.claude/memory/personal/. An earlier session had set autoMemoryDirectory: ~/.claude/memory/personal/ via the skill from inside that same folder. Obvious in retrospect; not obvious at all in the moment.
The safeguard is now the last gate before Apply.
General lesson: for any tool that wires a config key A to a filesystem location B, check whether A could resolve to a location that contains the tool’s own working scope. Circular configurations rarely throw; they degrade silently.
The conventions these decisions all share
The four decisions above share two conventions that recur across my personal skills:
Read-before-write on user state. Settings, history, credentials, anything the user typed once and expects to still be there. The cost (one extra Read, one extra Edit vs. Write) is trivial. The cost of getting it wrong is “restore from git reflog.”
AskUserQuestion as the primary interaction mechanism. Flags are great when you remember them. You will not. Six weeks later you will be asking what that one flag actually did. The picker answers in 2 seconds, in the transcript, with the choices visible.
Both conventions also apply to nf-dream and nf-git-workflow. Skill-design defaults you adopt once and never regret.
What you should keep even if you never use my skill
Four patterns generalize cleanly:
- Read before write on any file with user state. Settings, history, credentials, lock files. Never
Writea populated file you have not firstRead. - An interactive picker beats a magic default for personal-preference settings. If the right answer depends on the user’s broader workflow, make it a question, not an assumption.
- Migration tools need smart merge, never blind copy. Walk per-file. Detect filename and topic conflicts. Surface contradictions rather than silently picking. Leave sources untouched until the user confirms.
- Detect circular / self-referential configurations early, with a clear error. Wire-it-to-itself bugs rarely throw at runtime; they degrade silently. Catch them at configuration time.
The skill is one encoding of those patterns. The patterns are the value.
Where this fits in the wider rule-and-skill setup
nf-memory writes new memory into a folder. The companion rule user-memory-frontmatter (in my host setup) tells the session what to write into each new memory file: a canonical YAML block with metadata.type, metadata.project, metadata.cwd, metadata.tags, metadata.related. The skill configures the destination, the rule structures the content, and nf-dream later relies on metadata.project as the source of truth for per-project filtering.
The combination keeps nf-dream near-instant on warm folders because per-file project filtering becomes a YAML parse instead of an in-session classification.
Get the skill
White-labeled, ready to drop into ~/.claude/skills/. Published in the claude-skills-toolkit repo alongside nf-agents, nf-dream, and nf-git-workflow.
git clone https://github.com/llawliet11/claude-skills-toolkit.git
cp -r claude-skills-toolkit/nf-memory ~/.claude/skills/
Folder reference: claude-skills-toolkit/nf-memory.
Verify in a new Claude Code session: type /nf-memory and the skill should appear in the slash-command list. The skill’s own README.md covers the .gitignore hygiene check and the recursive-memory safeguard.
If you find a failure mode I have not yet hit, open an issue on the repo.