-
Notifications
You must be signed in to change notification settings - Fork 156
fix: respect CLAUDE_CONFIG_DIR for claude target user-scope deploy #1055
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -214,6 +214,25 @@ def for_scope(self, user_scope: bool = False) -> "TargetProfile | None": | |
| return None | ||
|
|
||
| new_root = self.user_root_dir or self.root_dir | ||
|
|
||
| # Claude Code honors CLAUDE_CONFIG_DIR (default ~/.claude); mirror | ||
| # that at user scope so `apm install -g` lands where Claude reads. | ||
| if self.name == "claude": | ||
| import os | ||
| from pathlib import Path | ||
|
|
||
| env = os.environ.get("CLAUDE_CONFIG_DIR", "").strip() | ||
| if env: | ||
| # ``resolve`` collapses ``..`` so traversal segments cannot | ||
| # leak into ``root_dir`` and escape ``project_root / root_dir``. | ||
| abs_path = Path(env).expanduser().resolve(strict=False) | ||
| home = Path.home().resolve(strict=False) | ||
| try: | ||
| # Keep ``root_dir`` home-relative so cleanup prefix matching holds. | ||
| new_root = abs_path.relative_to(home).as_posix() | ||
| except ValueError: | ||
| new_root = str(abs_path) | ||
|
|
||
| if self.unsupported_user_primitives: | ||
| filtered = { | ||
| k: v for k, v in self.primitives.items() | ||
|
|
@@ -259,10 +278,12 @@ def for_scope(self, user_scope: bool = False) -> "TargetProfile | None": | |
| user_root_dir=".copilot", | ||
| unsupported_user_primitives=("prompts", "instructions"), | ||
| ), | ||
| # Claude Code -- ~/.claude/ is the documented user-level config directory. | ||
| # 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. | ||
|
shuntaka9576 marked this conversation as resolved.
|
||
| # Ref: https://docs.anthropic.com/en/docs/claude-code/settings | ||
|
Comment on lines
+281
to
285
|
||
| # Instructions deploy to .claude/rules/*.md with paths: frontmatter. | ||
| # Instructions deploy to <root>/rules/*.md with paths: frontmatter. | ||
| # Ref: https://code.claude.com/docs/en/memory#organize-rules-with-claude%2Frules%2F | ||
| "claude": TargetProfile( | ||
| name="claude", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -331,11 +331,11 @@ def test_project_scope(self): | |
| for p in deployed: | ||
| assert not (self.project_root / p).exists() | ||
|
|
||
| def test_user_scope(self): | ||
| def test_user_scope(self, monkeypatch): | ||
| """Claude user scope: same root (.claude/), all primitives available.""" | ||
| monkeypatch.delenv("CLAUDE_CONFIG_DIR", raising=False) | ||
| target = KNOWN_TARGETS["claude"].for_scope(user_scope=True) | ||
|
shuntaka9576 marked this conversation as resolved.
|
||
| assert target is not None | ||
| # Claude has no user_root_dir so root stays .claude | ||
| assert target.root_dir == ".claude" | ||
| # All primitives available at user scope | ||
| assert "instructions" in target.primitives | ||
|
|
@@ -401,6 +401,33 @@ def test_user_scope(self): | |
| for p in deployed: | ||
| assert not (self.project_root / p).exists() | ||
|
|
||
| def test_user_scope_with_claude_config_dir(self, monkeypatch): | ||
| """CLAUDE_CONFIG_DIR override: deploy lands at custom root and uninstall cleans it.""" | ||
| 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) | ||
|
Comment on lines
+406
to
+409
|
||
|
|
||
| target = KNOWN_TARGETS["claude"].for_scope(user_scope=True) | ||
| assert target is not None | ||
| assert target.root_dir == ".config/test-claude" | ||
|
|
||
| pkg_info = _make_pkg(self.project_root, instructions=False, agents=True) | ||
| integrator = AgentIntegrator() | ||
|
|
||
| result = integrator.integrate_agents_for_target(target, pkg_info, self.project_root) | ||
| deployed = _posix_relpaths(self.project_root, result.target_paths) | ||
| assert deployed | ||
| for p in deployed: | ||
| assert p.startswith(".config/test-claude/agents/"), f"unexpected path: {p}" | ||
|
|
||
| sync = integrator.sync_for_target( | ||
| target, pkg_info.package, self.project_root, managed_files=deployed | ||
| ) | ||
| assert sync["files_removed"] == len(deployed) | ||
| for p in deployed: | ||
| assert not (self.project_root / p).exists() | ||
|
|
||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Cursor | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.