fix: respect CLAUDE_CONFIG_DIR for claude target user-scope deploy#1055
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes Claude user-scope integration so global installs can deploy into the directory Claude Code actually reads when CLAUDE_CONFIG_DIR is set, rather than always writing to ~/.claude.
Changes:
- Add
CLAUDE_CONFIG_DIRhandling for theclaudetarget duringTargetProfile.for_scope(user_scope=True). - Add/adjust unit tests to cover env-var override semantics and to pin
CLAUDE_CONFIG_DIRunset in other scope-resolution tests for determinism. - Update inline target registry comments for Claude to reflect the new behavior.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/apm_cli/integration/targets.py |
Adds user-scope root override for the claude target based on CLAUDE_CONFIG_DIR; updates Claude target comments. |
tests/unit/integration/test_targets.py |
Adds new test class covering CLAUDE_CONFIG_DIR behavior for Claude user scope. |
tests/unit/integration/test_scope_integration.py |
Pins CLAUDE_CONFIG_DIR unset to keep existing scope resolution assertions stable. |
tests/unit/integration/test_scope_install_uninstall.py |
Pins CLAUDE_CONFIG_DIR unset for existing Claude user-scope install/uninstall test. |
tests/unit/integration/test_data_driven_dispatch.py |
Pins CLAUDE_CONFIG_DIR unset for the “user_root_dir is None” behavior test (Claude). |
|
@microsoft-github-policy-service agree |
814e680 to
8063b5f
Compare
8063b5f to
e4f4e54
Compare
| monkeypatch.setenv("HOME", str(self.project_root)) | ||
| custom = self.project_root / ".config" / "test-claude" | ||
| monkeypatch.setenv("CLAUDE_CONFIG_DIR", str(custom)) | ||
| custom.mkdir(parents=True) |
There was a problem hiding this comment.
This test patches only HOME to make Path.home() point at self.project_root, but on Windows Path.home() typically prefers USERPROFILE over HOME. In that case for_scope(user_scope=True) will treat CLAUDE_CONFIG_DIR as outside home and return an absolute root_dir, making this assertion fail on the Windows unit-test job. Consider also patching USERPROFILE (and/or HOMEDRIVE/HOMEPATH) when sys.platform == "win32", consistent with the repo's other cross-platform home-override helpers.
| # Claude Code -- the user-level config directory is whatever | ||
| # ``CLAUDE_CONFIG_DIR`` points to (default ``~/.claude``). The env | ||
| # var override is honored by ``for_scope(user_scope=True)``. | ||
| # All primitives are supported at user scope. | ||
| # Ref: https://docs.anthropic.com/en/docs/claude-code/settings |
There was a problem hiding this comment.
User-scope Claude deploy root is no longer always ~/.claude/ (it can be overridden by CLAUDE_CONFIG_DIR). Several Starlight docs under docs/src/content/docs/ still hardcode ~/.claude/ for global installs (e.g. guides/dependencies.md and integrations/ide-tool-integration.md). Please update the relevant docs to mention the env-var override (keeping it concise) so guidance matches the new behavior.
…deploy Resolve $CLAUDE_CONFIG_DIR into a home-relative root_dir at user scope so claude deploys land where Claude reads and cleanup's prefix matching keeps holding. resolve() collapses `..` before relative_to to prevent traversal escapes. Out-of-$HOME values fall through unchanged.
…ool-integration Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
e4f4e54 to
c95e9e8
Compare
Maintainer rebase + panel verdict (per #1064 FIX-NOW orchestration)Rebased on Verdict: READY Panel findings (7 personas)
Test evidencePR's own Merging via squash next. |
danielmeppiel
left a comment
There was a problem hiding this comment.
Approving — all required checks SUCCESS on c95e9e8 with the panel-doc commit. Doc-writer additions cover Claude config dir support in user-facing docs.
…#1065) * fix: address Copilot review findings on #1055 and #991 (consolidated) Folds the still-actionable findings from the post-merge Copilot reviews of PRs #1055 and #991 into a single PR (no follow-up issue / PR sprawl). #1055 (CLAUDE_CONFIG_DIR support): - Document why the absolute-path fallback in TargetProfile.for_scope() is safe for downstream consumers (pathlib's 'rhs absolute wins' rule on '/' joins). Comment-only; no behavior change. - Findings already addressed by the original PR (verified): * resolve(strict=False) collapses '..' before relative_to(home) * docs/guides/dependencies.md + integrations/ide-tool-integration.md already cover CLAUDE_CONFIG_DIR in the merged version * test_scope_install_uninstall.py:382 + test_scope_integration.py already exercise install/uninstall + outside-home + traversal cases #991 (link_resolver guards): - Correct the embedded-NUL-byte test docstring: Path.exists() raises ValueError on NUL on most platforms (it does not silently return False). Cosmetic; test behavior unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address Copilot review on #1065 Two valid findings from copilot-pull-request-reviewer: 1. link_resolver._resolve_path: NUL byte in input previously returned a Path that crashed downstream .exists() / .read_text() with ValueError, aborting markdown link resolution. Reject NUL at the resolver boundary so callers (resolve_markdown_links, validate_link_targets) get None as documented. Tightened test to assert None instead of 'either is fine'. 2. integration/targets.for_scope: prior comment claimed absolute root_dir was 'safe' for all downstream consumers. install/services. _deployed_path_entry would actually raise RuntimeError for an out-of-tree absolute path. Today this is unreachable because user-scope CLAUDE installs do not flow through that translator, but the comment now records the constraint so a future refactor that lockfiles user-scope deploys treats it as a dynamic-root case. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test(windows): patch USERPROFILE so Path.home() honors fake HOME The CLAUDE_CONFIG_DIR scope tests added in #1055 patched only HOME, but Path.home() on Windows uses USERPROFILE (with HOMEDRIVE+HOMEPATH as fallback) and ignores HOME. As a result, Path.home() returned the real runner profile, relative_to(home) succeeded against the AppData/Local tmp_path, and the assertions on root_dir failed on windows-latest. Add a small _set_home() helper in both scope test modules that also sets USERPROFILE / HOMEDRIVE / HOMEPATH on Windows, and use it in the three failing tests: - test_user_scope_with_claude_config_dir - test_user_scope_outside_home_keeps_absolute - test_user_scope_collapses_dotdot_segments plus test_user_scope_expands_tilde for consistency. Also resolve() the expected outside path in test_user_scope_outside_home_keeps_absolute to match the source's abs_path.expanduser().resolve() output (e.g. /var -> /private/var on macOS, 8.3 short-name expansion on Windows). Fixes failures from CI run 25190857376. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: address review nits on outside-home assertion - Update comment to reflect that paths outside $HOME are resolved (normalized), not preserved verbatim. - Make the .resolve() call explicit with strict=False to mirror the implementation in for_scope() and document the expected behavior for non-existent paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <copilot-rework@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Description
Claude Code reads its config from
$CLAUDE_CONFIG_DIR(default~/.claude), butapm install -g --target claudealways wrote to~/.claude/. Users who setCLAUDE_CONFIG_DIR=~/.config/claudesaw APM's deploys land in a tree Claude Code never reads.TargetProfile.for_scope(user_scope=True)now resolves$CLAUDE_CONFIG_DIRinto a home-relative posix string for claude'sroot_dir, preserving the project_root-relative prefix invariant used bypartition_managed_files/validate_deploy_path. Deploy and cleanup both honor the override.Type of change
Out of scope
CLAUDE_CONFIG_DIRvalues outside$HOMEare intentionally not supported. At user scopeproject_root = Path.home(), so out-of-$HOMEpaths can't be expressed relative to project_root and the lockfile step fails for them. Claude Code's documented configurations (~/.claude,~/.config/claude) all live under$HOME; covering arbitrary paths would need aclaude://URI scheme analogous tocowork://, which is much larger than this fix.The fall-through (absolute string in
root_dir) is pinned bytest_user_scope_outside_home_keeps_absoluteso it can be replaced cleanly when the URI scheme arrives.Testing
uv run pytest tests/unit— passed (6706 tests).End-to-end deploy + uninstall under
$CLAUDE_CONFIG_DIR:All seven skills land under
$CLAUDE_CONFIG_DIR,~/.claude/is untouched, and uninstall removes them via prefix matching.Test additions:
test_scope_integration.py::TestClaudeScopeResolution— env-var override cases (~expansion, blank fallback, out-of-$HOMEfall-through, project-scope ignore).test_scope_install_uninstall.py::test_user_scope_with_claude_config_dir— install→sync_for_targetcycle underCLAUDE_CONFIG_DIR, locking the prefix-matching contract for cleanup.test_data_driven_dispatch.py,test_scope_install_uninstall.py, andtest_scope_integration.pyare pinned withmonkeypatch.delenv("CLAUDE_CONFIG_DIR", raising=False)so the default behaviour is exercised regardless of the developer's shell.