mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
## Description `headroom learn` ignored `CLAUDE_CONFIG_DIR`. `ClaudeCodePlugin.__init__` resolved the Claude config directory as `~/.claude`, and the memory writer wrote the global `CLAUDE.md` to `~/.claude/CLAUDE.md`. A user who relocates their Claude config with that env var had `learn` scan the wrong directory and detect no projects. Other parts of the codebase already honor the override (`subscription/client.py`, `subscription/session_tracking.py`, `mcp_registry/claude.py`); the `learn` path was the outlier. Closes #1630 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) ## Changes Made - Add `claude_config_dir()` to `headroom/learn/_shared.py` — returns `$CLAUDE_CONFIG_DIR` when set, else `~/.claude` (via `Path.home()`, matching the existing override elsewhere). - `ClaudeCodePlugin.__init__` now defaults `claude_dir` to `claude_config_dir()` instead of a hardcoded `~/.claude` (an explicit `claude_dir=` argument still wins). - `ClaudeCodeWriter._resolve_context_path` writes the home-directory global memory to `claude_config_dir() / "CLAUDE.md"` instead of `~/.claude/CLAUDE.md`. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check .`) - [x] Type checking passes (`mypy headroom`) - [x] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text $ pytest tests/test_learn/test_claude_config_dir.py tests/test_learn/test_writer.py -q 35 passed, 1 warning in 0.18s $ ruff check headroom/learn/ tests/test_learn/test_claude_config_dir.py All checks passed! $ mypy headroom/learn/_shared.py headroom/learn/plugins/claude.py headroom/learn/writer.py Success: no issues found in 3 source files ``` ## Real Behavior Proof - Environment: macOS (arm64), Python 3.14 venv, editable install of this branch. - Exact command / steps: ran `python -c "from headroom.learn.plugins.claude import ClaudeCodePlugin; print(ClaudeCodePlugin().projects_dir)"` with and without `CLAUDE_CONFIG_DIR=/tmp/altclaude` set, then `pytest tests/test_learn/ tests/test_cli_learn.py`. - Observed result: default prints `/Users/<me>/.claude/projects`; with `CLAUDE_CONFIG_DIR=/tmp/altclaude` it prints `/tmp/altclaude/projects` (before this change the second still printed `~/.claude/projects`). Test suite: 226 passed, 3 skipped. New regression tests cover the plugin scan dir, explicit-arg precedence, and the writer's home-memory path. - Not tested: end-to-end `headroom learn` against a real relocated log tree with live Claude Code transcripts — verified at the plugin/writer resolution layer plus the existing scanner suite. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Additional Notes The identical hardcode also exists at `headroom/cli/mcp.py:21` (`CLAUDE_CONFIG_DIR = Path.home() / ".claude"`), but that is a separate command outside this issue's scope, so I left it for a follow-up to keep this PR to one issue. --------- Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
54 lines
2 KiB
Python
54 lines
2 KiB
Python
"""Regression tests: `headroom learn` honors CLAUDE_CONFIG_DIR (issue #1630).
|
|
|
|
Claude Code relocates its config (conversation logs under ``projects/`` and the
|
|
global ``CLAUDE.md``) when ``CLAUDE_CONFIG_DIR`` is set. The learn scanner and
|
|
memory writer previously hardcoded ``~/.claude``, so they scanned/wrote the
|
|
wrong directory and detected no projects for such users.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from headroom.learn._shared import claude_config_dir
|
|
from headroom.learn.models import ProjectInfo
|
|
from headroom.learn.plugins.claude import ClaudeCodePlugin
|
|
from headroom.learn.writer import ClaudeCodeWriter
|
|
|
|
|
|
def test_config_dir_honors_env(tmp_path, monkeypatch) -> None:
|
|
custom = tmp_path / "custom-claude"
|
|
monkeypatch.setenv("CLAUDE_CONFIG_DIR", str(custom))
|
|
assert claude_config_dir() == custom
|
|
|
|
|
|
def test_config_dir_defaults_to_home(monkeypatch) -> None:
|
|
monkeypatch.delenv("CLAUDE_CONFIG_DIR", raising=False)
|
|
assert claude_config_dir() == Path.home() / ".claude"
|
|
|
|
|
|
def test_plugin_scans_config_dir_override(tmp_path, monkeypatch) -> None:
|
|
custom = tmp_path / "custom-claude"
|
|
monkeypatch.setenv("CLAUDE_CONFIG_DIR", str(custom))
|
|
plugin = ClaudeCodePlugin()
|
|
assert plugin.claude_dir == custom
|
|
assert plugin.projects_dir == custom / "projects"
|
|
|
|
|
|
def test_plugin_explicit_dir_wins_over_env(tmp_path, monkeypatch) -> None:
|
|
monkeypatch.setenv("CLAUDE_CONFIG_DIR", str(tmp_path / "env"))
|
|
explicit = tmp_path / "explicit"
|
|
plugin = ClaudeCodePlugin(claude_dir=explicit)
|
|
assert plugin.claude_dir == explicit
|
|
|
|
|
|
def test_writer_home_memory_follows_config_dir(tmp_path, monkeypatch) -> None:
|
|
custom = tmp_path / "custom-claude"
|
|
monkeypatch.setenv("CLAUDE_CONFIG_DIR", str(custom))
|
|
writer = ClaudeCodeWriter()
|
|
project = ProjectInfo(
|
|
name="home",
|
|
project_path=Path.home(),
|
|
data_path=custom / "projects",
|
|
)
|
|
assert writer._resolve_context_path(project) == custom / "CLAUDE.md"
|