Bug Description
The Claude hook code resolves the user-level settings file as a hardcoded ~/.claude/settings.json and never consults the CLAUDE_CONFIG_DIR environment variable. Claude Code treats CLAUDE_CONFIG_DIR as a full replacement for ~/.claude, so anyone running more than one Claude profile (separate accounts / config dirs, selected per project tree) hits two problems:
- Settings are invisible. A
basicMemory block in the active profile's settings.json is never read. hook status reports settings: not found and primary project: (not set), and capture silently falls back to the default project.
bm hook install writes to the wrong profile. It edits ~/.claude/settings.json regardless of which profile is active, so installing hooks while working under profile B modifies profile A's configuration — a different account's settings file.
There is no CLAUDE_CONFIG_DIR reference anywhere in the package:
$ grep -rn "CLAUDE_CONFIG_DIR" .../site-packages/basic_memory/
(no matches)
Two sites are affected, both in basic_memory/cli/commands/hook.py:
load_claude_settings() — hook.py:254-255
home = Path.home()
sources: list[tuple[Path, tuple[str, ...]]] = [(home, ("settings.json",))]
_hook_config_path() — hook.py:1252
if harness is Harness.claude:
return Path.home() / ".claude" / "settings.json"
The environment variable is available to the hook process — Claude Code is launched with it exported, and hooks inherit the environment — so this is only a matter of reading it.
Steps To Reproduce
- Install basic-memory 0.23.2 and wire the MCP server into Claude Code.
- Create a second Claude profile in a non-default config dir, e.g.
~/.claude-orga, and launch Claude Code with CLAUDE_CONFIG_DIR=~/.claude-orga.
- Put a valid
basicMemory block in ~/.claude-orga/settings.json:
{ "basicMemory": { "primaryProject": "orgA", "captureFolder": "sessions" } }
- Ensure no
.claude/settings.json or .claude/settings.local.json exists in the project tree.
- Run:
basic-memory hook status --harness claude --project-dir ~/Workspace/orgA/notes
Expected Behavior
The block in ~/.claude-orga/settings.json is read as the user-level base, and hook status reports:
settings (claude, ~/Workspace/orgA/notes): found
primary project: orgA
Correspondingly, bm hook install should write to $CLAUDE_CONFIG_DIR/settings.json.
Actual Behavior
settings (claude, ~/Workspace/orgA/notes): not found
primary project: (not set)
The block is ignored. Moving the same block to a project-level .claude/settings.local.json makes it resolve correctly, confirming only the user-level base is affected.
The second site is worse in effect: with profile orgA active, bm hook install writes hook entries into ~/.claude/settings.json, which belongs to profile orgB. Nothing indicates the wrong file was touched.
Environment
- OS: macOS 15 (Darwin 25.6.0, arm64)
- Python version: 3.12 (uv-managed tool environment)
- Basic Memory version: 0.23.2
- Installation method:
uv tool install basic-memory
- Claude Code with the
basic-memory plugin from basicmachines-co/basic-memory
Additional Context
There is a related correctness issue beyond the two above. Because ~/.claude/settings.json is the shared user-level base for every profile, a multi-profile user cannot safely put a primaryProject there at all: a default meant for profile B is also read while working under profile A, so notes can be routed into the wrong project's graph. Honoring CLAUDE_CONFIG_DIR removes that coupling, since each profile then has its own base.
Workaround, which may be worth documenting regardless: place the basicMemory block in a project-level .claude/settings.local.json at the root of each workspace. _claude_project_dir() walks ancestors, so a single file at the top of a workspace covers every checkout beneath it, and it does not leak to sibling trees. Keep outputStyle in the profile's own settings.json — Claude Code does honor CLAUDE_CONFIG_DIR for its own settings, so only the basicMemory block needs relocating.
Possible Solution
Add a helper and use it at both sites:
def _claude_user_dir() -> Path:
"""User-level Claude config dir. CLAUDE_CONFIG_DIR replaces ~/.claude entirely."""
override = os.environ.get("CLAUDE_CONFIG_DIR", "").strip()
return Path(override).expanduser() if override else Path.home() / ".claude"
load_claude_settings(): use it for the user-level source, and for the project != home comparison so a project sitting at the config dir is not added twice.
_hook_config_path(): return _claude_user_dir() / "settings.json".
Falling back to ~/.claude when the variable is unset keeps existing single-profile setups unchanged. Happy to send a PR if that direction looks right.
Bug Description
The Claude hook code resolves the user-level settings file as a hardcoded
~/.claude/settings.jsonand never consults theCLAUDE_CONFIG_DIRenvironment variable. Claude Code treatsCLAUDE_CONFIG_DIRas a full replacement for~/.claude, so anyone running more than one Claude profile (separate accounts / config dirs, selected per project tree) hits two problems:basicMemoryblock in the active profile'ssettings.jsonis never read.hook statusreportssettings: not foundandprimary project: (not set), and capture silently falls back to the default project.bm hook installwrites to the wrong profile. It edits~/.claude/settings.jsonregardless of which profile is active, so installing hooks while working under profile B modifies profile A's configuration — a different account's settings file.There is no
CLAUDE_CONFIG_DIRreference anywhere in the package:Two sites are affected, both in
basic_memory/cli/commands/hook.py:load_claude_settings()—hook.py:254-255_hook_config_path()—hook.py:1252The environment variable is available to the hook process — Claude Code is launched with it exported, and hooks inherit the environment — so this is only a matter of reading it.
Steps To Reproduce
~/.claude-orga, and launch Claude Code withCLAUDE_CONFIG_DIR=~/.claude-orga.basicMemoryblock in~/.claude-orga/settings.json:{ "basicMemory": { "primaryProject": "orgA", "captureFolder": "sessions" } }.claude/settings.jsonor.claude/settings.local.jsonexists in the project tree.Expected Behavior
The block in
~/.claude-orga/settings.jsonis read as the user-level base, andhook statusreports:Correspondingly,
bm hook installshould write to$CLAUDE_CONFIG_DIR/settings.json.Actual Behavior
The block is ignored. Moving the same block to a project-level
.claude/settings.local.jsonmakes it resolve correctly, confirming only the user-level base is affected.The second site is worse in effect: with profile
orgAactive,bm hook installwrites hook entries into~/.claude/settings.json, which belongs to profileorgB. Nothing indicates the wrong file was touched.Environment
uv tool install basic-memorybasic-memoryplugin frombasicmachines-co/basic-memoryAdditional Context
There is a related correctness issue beyond the two above. Because
~/.claude/settings.jsonis the shared user-level base for every profile, a multi-profile user cannot safely put aprimaryProjectthere at all: a default meant for profile B is also read while working under profile A, so notes can be routed into the wrong project's graph. HonoringCLAUDE_CONFIG_DIRremoves that coupling, since each profile then has its own base.Workaround, which may be worth documenting regardless: place the
basicMemoryblock in a project-level.claude/settings.local.jsonat the root of each workspace._claude_project_dir()walks ancestors, so a single file at the top of a workspace covers every checkout beneath it, and it does not leak to sibling trees. KeepoutputStylein the profile's ownsettings.json— Claude Code does honorCLAUDE_CONFIG_DIRfor its own settings, so only thebasicMemoryblock needs relocating.Possible Solution
Add a helper and use it at both sites:
load_claude_settings(): use it for the user-level source, and for theproject != homecomparison so a project sitting at the config dir is not added twice._hook_config_path():return _claude_user_dir() / "settings.json".Falling back to
~/.claudewhen the variable is unset keeps existing single-profile setups unchanged. Happy to send a PR if that direction looks right.