Skip to content

Commit 65ae2e8

Browse files
Altruistusclaude
andcommitted
CM-71972: Resolve the skills directory at call time, not at import
CI caught a real bug. The skills directory was a module-level constant, so Path.home() was evaluated when the module was imported - pinning it to whatever home the process started with. A test filesystem that redirects home could then never be seen, which is why the collection test passed on my machine and on some runners and failed on others: it depended on whether the fake home happened to match the real one. Every other home-relative directory in this module is already a function for exactly this reason, so this follows _plugins_cache_dir. The regression test patches home and asserts the directory follows; it fails against the old constant, so the bug cannot come back silently. Also fixes the lint failure: a lambda returning an empty list is just list. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 79006fb commit 65ae2e8

3 files changed

Lines changed: 28 additions & 3 deletions

File tree

‎cycode/cli/apps/ai_guardrails/ides/claude_code.py‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434

3535
_CLAUDE_CONFIG_PATH = Path.home() / '.claude.json'
3636
_CLAUDE_SETTINGS_PATH = Path.home() / '.claude' / 'settings.json'
37-
_CLAUDE_SKILLS_DIR = Path.home() / '.claude' / 'skills'
3837

3938
_SCAN_COMMAND = f'{CYCODE_SCAN_PROMPT_COMMAND} --ide claude-code'
4039
_SESSION_START_COMMAND = f'{CYCODE_SESSION_START_COMMAND} --ide claude-code'
@@ -171,6 +170,15 @@ def load_claude_settings(settings_path: Optional[Path] = None) -> Optional[dict]
171170
return None
172171

173172

173+
def _claude_skills_dir() -> Path:
174+
"""Claude Code's user-scope skills: ``~/.claude/skills/<name>/SKILL.md``.
175+
176+
A function, not a module constant: resolving ``Path.home()`` at import time pins the directory to
177+
whatever home the process started with, which a test filesystem then cannot redirect.
178+
"""
179+
return Path.home() / '.claude' / 'skills'
180+
181+
174182
def _plugins_cache_dir() -> Path:
175183
"""Claude Code's local plugin content cache: ``~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/``."""
176184
return Path.home() / '.claude' / 'plugins' / 'cache'
@@ -398,4 +406,4 @@ def get_session_context(self) -> tuple[Optional[dict], dict]:
398406
return global_config_file, enriched_plugins
399407

400408
def get_skills(self) -> list[dict]:
401-
return walk_skill_dirs(_CLAUDE_SKILLS_DIR)
409+
return walk_skill_dirs(_claude_skills_dir())

‎tests/cli/commands/ai_guardrails/ides/test_skill_utils.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from pathlib import Path
44

5+
import pytest
56
from pyfakefs.fake_filesystem import FakeFilesystem
67

78
from cycode.cli.apps.ai_guardrails.ides._skill_utils import (
@@ -92,3 +93,19 @@ def test_walk_skill_dirs_truncates_at_count_cap(fs: FakeFilesystem) -> None:
9293
fs.create_file(root / f'skill-{index:04d}' / 'SKILL.md', contents=_BODY)
9394

9495
assert len(walk_skill_dirs(root)) == MAX_SKILLS_PER_ROOT
96+
97+
98+
def test_walk_skill_dirs_follows_the_current_home(fs: FakeFilesystem, monkeypatch: pytest.MonkeyPatch) -> None:
99+
"""Regression: the skills directory must resolve when called, not when the module is imported.
100+
101+
It was a module-level constant, so Path.home() was captured at import and a redirected home could
102+
never be seen - which passed locally and failed on CI runners whose home differed.
103+
"""
104+
from cycode.cli.apps.ai_guardrails.ides.claude_code import ClaudeCode, _claude_skills_dir
105+
106+
relocated = Path('/relocated-home')
107+
fs.create_file(relocated / '.claude' / 'skills' / 'moved-skill' / 'SKILL.md', contents=_BODY)
108+
monkeypatch.setattr(Path, 'home', staticmethod(lambda: relocated))
109+
110+
assert _claude_skills_dir() == relocated / '.claude' / 'skills'
111+
assert [Path(s['path']).parent.name for s in ClaudeCode().get_skills()] == ['moved-skill']

‎tests/cli/commands/ai_guardrails/test_session_start_command.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def _no_local_skills(monkeypatch: pytest.MonkeyPatch) -> None:
3939
filesystem directly - so without this every assertion would depend on whoever ran it.
4040
A test that cares about skills overrides ``collect_all_skills`` itself.
4141
"""
42-
monkeypatch.setattr(_session_start_mod, 'collect_all_skills', lambda: [])
42+
monkeypatch.setattr(_session_start_mod, 'collect_all_skills', list)
4343

4444

4545
# Auth tests

0 commit comments

Comments
 (0)