fix(install): respect $CLAUDE_CONFIG_DIR in install/uninstall/update#321
Open
halindrome wants to merge 1 commit intoDeusData:mainfrom
Open
fix(install): respect $CLAUDE_CONFIG_DIR in install/uninstall/update#321halindrome wants to merge 1 commit intoDeusData:mainfrom
halindrome wants to merge 1 commit intoDeusData:mainfrom
Conversation
Previously, `codebase-memory-mcp install` and `codebase-memory-mcp update`
unconditionally wrote skills, MCP entries, hook scripts, and settings.json
under $HOME/.claude/, even when CLAUDE_CONFIG_DIR pointed at a different
location. Users with non-default Claude Code config dirs ended up with a
new (silently created) ~/.claude/ tree while their real config dir stayed
untouched, so the agent could not see the artifacts the installer claimed
to write.
This commit:
- Adds cbm_claude_config_dir(), cbm_claude_user_root(), and
cbm_resolve_hook_command() helpers in src/cli/cli.c that honor
$CLAUDE_CONFIG_DIR with $HOME/.claude as the legacy fallback.
- Routes install_claude_code_config(), uninstall_claude_code(),
cbm_install_hook_gate_script(), cbm_install_session_reminder_script(),
and cbm_detect_agents()'s claude_code probe through those helpers.
- Replaces the CMM_HOOK_COMMAND / CMM_SESSION_COMMAND macro literals
("~/.claude/hooks/...") with runtime-resolved hook command strings
written into settings.json. The legacy tilde form is preserved when
CLAUDE_CONFIG_DIR is unset so existing settings.json files stay
portable across HOME values.
- Mirrors the change in scripts/setup.sh (settings_file path) and
scripts/security-install.sh (SKILLS_DIR path) so the shell-side
helpers also honor CLAUDE_CONFIG_DIR.
Bundled migration safeguard:
- When install runs with CLAUDE_CONFIG_DIR set and a legacy ~/.claude
directory still exists, install emits a one-line stderr nudge naming
both paths so users can clean up stale skills/hooks/settings without
needing a follow-up release.
Tests:
- Adds cli_detect_agents_finds_claude_via_env to verify CLAUDE_CONFIG_DIR
detection. Updates cli_detect_agents_finds_claude and
cli_detect_agents_none_found to unset/restore CLAUDE_CONFIG_DIR so
the runner's real env does not leak in.
- Adds Phase 9c to scripts/smoke-test.sh covering: skills/.mcp.json/
.claude.json/settings.json/hooks all landing under $CLAUDE_CONFIG_DIR
(not $HOME/.claude), settings.json hook command path, migration
nudge surface, uninstall removal, legacy ~/.claude untouched, and
legacy default preserving the tilde form when CLAUDE_CONFIG_DIR is
unset.
Closes DeusData#320
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
Author
QA Round 1 — PR #321 (
|
| # | What | Status | Evidence / Notes |
|---|---|---|---|
| 1 | Helper trio correct (buffer size handling, NULL guards, env precedence, tilde preserved only when env unset) | PASS | All three helpers (cbm_claude_config_dir, cbm_claude_user_root, cbm_resolve_hook_command) early-return on out_sz==0, zero-init out[0]='\0', branch on cbm_safe_getenv (non-empty wins) then fall back to home_dir. cbm_resolve_hook_command emits ~/.claude/hooks/<name> only when env unset. |
| 2 | Every ~/.claude/... write site routed through helpers |
PASS | Remaining literals in src/cli/cli.c: lines 973/984/988/1017 (helper bodies), 1692 (comment), 2715/2736/3048 (paths derived from home-based legacy_dir/mcp_path2/comment — all correct uses). cli.h lines 67/113/168 are doc comments. No missed callsite. |
| 3 | .claude.json placement: $CCD/.claude.json when env set, else $HOME/.claude.json |
PASS | cbm_claude_user_root returns env-or-home; mcp_path2 is <user_root>/.claude.json in both install_claude_code_config (line 2715) and uninstall_claude_code (line 3048). Manual T1 confirms .claude.json written under $CCD, no stray $HOME/.claude.json. |
| 4 | Hook command in settings.json: tilde when env unset, absolute when env set |
PASS | Manual T1: PreToolUse: $CCD/hooks/cbm-code-discovery-gate; SessionStart: $CCD/hooks/cbm-session-reminder. Manual T2 (env unset): PreToolUse: ~/.claude/hooks/cbm-code-discovery-gate; SessionStart: ~/.claude/hooks/cbm-session-reminder. |
| 5 | Removal symmetry — old ~/.claude/... entries replaced by env-set absolute path |
PASS | is_cmm_hook_entry matches by matcher_str only (line 1525). Manual T5: installed with env unset (1 PreToolUse entry, tilde cmd) → re-installed with CCD set → settings.json still has 1 PreToolUse entry, command rewritten to absolute path. SessionStart: still exactly 4 entries, all rewritten. |
| 6 | Migration nudge condition (CCD set AND legacy exists AND differs); no false positives | PASS | T1 (CCD set, legacy present, differs): nudge fires. T2 (CCD unset): silent. T3 (CCD set, no legacy dir): silent. T4 (CCD == $HOME/.claude): silent (the strcmp(legacy_dir, config_dir) != 0 guard handles the identical-path case). |
| 7 | Test coverage matches PR claims | PASS | New cli_detect_agents_finds_claude_via_env exercises env-set path with no home/.claude. Existing cli_detect_agents_finds_claude and cli_detect_agents_none_found now save/restore CLAUDE_CONFIG_DIR so the runner's real env can't leak. Phase 9c covers 10 placement/nudge/uninstall/symmetry assertions. |
| 8 | Shell layer minimal & correct | PASS | setup.sh swaps the literal for ${CLAUDE_CONFIG_DIR:-$HOME/.claude} plus updates the printed instruction text. security-install.sh swaps SKILLS_DIR. Both use the standard :- fallback idiom. No accidental side effects. |
| 9 | Style — conventional commits, code style consistent, no debug prints, no leftover TODO | PASS | All commits follow fix({scope}) pattern. Helpers follow the file's static-helper / snprintf / CLI_BUF_1K conventions. No printf debug prints. No // TODO. |
| 10 | Backwards compatibility — pre-existing settings.json with ~/.claude/hooks/... upserts cleanly |
PASS | T5 confirmed matcher-based dedup: only one PreToolUse entry survives the env-flip upgrade, with the command rewritten to the new absolute form. |
| 11 | Full test suite | PASS | scripts/test.sh: 2813 passed, 0 failed. |
Findings
Must-fix: None.
Nice-to-have (advisory only):
- Doc comment drift.
src/cli/cli.c:1692andsrc/cli/cli.h:{67,113,168}still describe paths as~/.claude/...even though install location is now CCD-aware. Comments are not load-bearing for behavior, but a one-line update to mention "or$CLAUDE_CONFIG_DIR" would prevent future grep confusion. - Migration nudge wording. The current nudge prints the absolute legacy path (e.g.
legacy /tmp/x/.claude still exists). If the user invoked install from a non-interactive context the absolute path is fine; for interactive use,~/.claudereads more cleanly. Strictly cosmetic. config_dir[0]==0guard divergence.cbm_install_hook_gate_scriptandcbm_install_session_reminder_scriptearly-return whenconfig_dir[0]is empty, butinstall_claude_code_configitself does not checkconfig_dir[0]before derivingskills_dir/mcp_path/settings_path. In practicehome==NULLis already filtered upstream, so this is unreachable, but adding a symmetricif (!config_dir[0]) return;would be defensive.cli.hcbm_detected_agents_t.claude_codecomment still says/* ~/.claude/ exists */; could read/* ~/.claude or $CLAUDE_CONFIG_DIR exists */to match the new detection semantics.
No security or correctness regression detected.
Final verdict
PASS-WITH-ADVISORY — the fix is correct, well-tested, and backwards-compatible. The four advisory items are documentation-comment polish, not behavior bugs. Recommend marking the PR ready for review.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
codebase-memory-mcp install(and thereforeupdate's Step 6 refresh) hardcoded$HOME/.claude/...for skills, MCP entries, hook scripts,.claude.json, andsettings.json. When users hadCLAUDE_CONFIG_DIRset to a non-default location (e.g.~/.config/claude-code, where Claude Code's own config lives), the installer silently created a separate~/.claude/tree that the agent never saw. Hooks installed at runtime already honoredCLAUDE_CONFIG_DIR; only the install/uninstall paths were mismatched.This PR fixes that. It also bundles a migration nudge so the same release that fixes the bug helps users with stale
~/.claude/trees clean them up — explained under "Bundling rationale" below.Closes #320.
What changes
Core helpers in
src/cli/cli.c:cbm_claude_config_dir(home, out, sz)— returns$CLAUDE_CONFIG_DIRor$home/.claude.cbm_claude_user_root(home, out, sz)— returns$CLAUDE_CONFIG_DIRor$home(parent dir for.claude.json, which moves to$CLAUDE_CONFIG_DIR/.claude.jsonwhen the env var is set, matching Claude Code's own behavior).cbm_resolve_hook_command(script_name, out, sz)— produces the hookcommandstring written intosettings.json. WhenCLAUDE_CONFIG_DIRis unset, it preserves the legacy~/.claude/hooks/<script>tilde form so existingsettings.jsonfiles stay portable acrossHOMEvalues.Call sites routed through the helpers:
install_claude_code_config— skills,.mcp.json,.claude.json,settings.json, hook script installuninstall_claude_code— same set, mirroredcbm_install_hook_gate_script,cbm_install_session_reminder_script— hook script destinationcbm_detect_agents—agents.claude_codenow true when either~/.claudeor$CLAUDE_CONFIG_DIRexistsThe
CMM_HOOK_COMMAND/CMM_SESSION_COMMANDmacros (which baked~/.claude/hooks/...into settings.json) are replaced byCMM_HOOK_GATE_SCRIPT/CMM_SESSION_REMINDER_SCRIPT(basenames), with the full path resolved at install time viacbm_resolve_hook_command. Removal still works becauseis_cmm_hook_entrymatches bymatcher_str, not by command path.Shell layer:
scripts/setup.sh—configure_claudenow uses${CLAUDE_CONFIG_DIR:-$HOME/.claude}forsettings_fileand the printed instructions.scripts/security-install.sh—SKILLS_DIRlikewise.Bundling rationale
This PR rolls out the fix and the migration nudge together rather than splitting them across two releases:
CLAUDE_CONFIG_DIRset would see their config silently migrate to the env-pointed dir. Their old~/.claude/{skills,hooks,settings.json,.mcp.json}would still be on disk — orphaned, never written again, easy to forget.installwhen (a)CLAUDE_CONFIG_DIRis set, and (b) a legacy~/.claudedirectory still exists. It names both paths so users can choose to clean up.The nudge is non-fatal, prints once per
installinvocation, and is silent in the legacy-default case (CLAUDE_CONFIG_DIRunset → no divergence to nudge about).Tests
tests/test_cli.c: newcli_detect_agents_finds_claude_via_envasserts thatcbm_detect_agentsreportsclaude_code=truewhenCLAUDE_CONFIG_DIRpoints at an existing dir outsidehome_dir/.claude.cli_detect_agents_finds_claudeandcli_detect_agents_none_foundnow unset/restoreCLAUDE_CONFIG_DIRso the runner's real env does not leak in. All 2813 unit tests pass.scripts/smoke-test.shPhase 9c: end-to-end install/uninstall underCLAUDE_CONFIG_DIR=$FAKE_HOME/custom-claudewith a pre-existing$FAKE_HOME/.claude. Asserts:skills/,settings.json,.mcp.json,.claude.json,hooks/cbm-code-discovery-gateall land under$CLAUDE_CONFIG_DIR.$HOME/.claude(regression guard).settings.jsonis the absolute$CLAUDE_CONFIG_DIR/hooks/...path, not~/.claude/hooks/....$CLAUDE_CONFIG_DIRand leaves legacy$HOME/.claudeuntouched.CLAUDE_CONFIG_DIRunset,settings.jsonkeeps the~/.claude/hooks/...tilde form (legacy default preserved).install+uninstallcycle with both env-set and env-unset configs, confirmed file placement and migration nudge surface match expectations.Out of scope (intentionally separate)
--scope=project): a real feature gap, but a separate change. Not bundled.EXPECTED_PATTERNSallow-list inscripts/security-install.shis unchanged;.config/...already matches typicalCLAUDE_CONFIG_DIRsetups, and broadening it without a concrete test is not worth the security audit risk.Test plan
scripts/build.shscripts/test.sh(2813 passed)install/uninstallwithCLAUDE_CONFIG_DIRsetinstall/uninstallwithCLAUDE_CONFIG_DIRunset (legacy default)_lint.yml(clang-format-20)_build.yml🤖 Generated with Claude Code