mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
## Description `headroom learn` crashes with an uncaught `PermissionError` when the current user's username contains a dash. `_decode_project_path` (in `headroom/learn/plugins/claude.py`) probes speculative candidate paths when reconstructing an original filesystem path from a Claude Code encoded project directory name. When the username is e.g. `marco-rocha`, one candidate becomes `/home/marco/rocha`, which can collide with another user's home directory whose parent isn't stat-able. `Path.exists()` calls `os.stat` internally, raising `PermissionError` instead of returning `False`, so the whole `learn` command crashes before returning any recommendations. Fixes #2443 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Add `_path_exists()` to `headroom/learn/plugins/claude.py` — a thin wrapper around `Path.exists()` that returns `False` on any `OSError` (including `PermissionError`), mirroring the existing `OSError` handling already used in `_greedy_path_decode`. - Route every speculative candidate-path existence check in the decode path through `_path_exists()`: the Windows drive/path probes in `_decode_windows_path`, the `simple` POSIX candidate and greedy-branch bases in `_decode_project_path`/`_greedy_path_decode`, and the decoded `project_path`/`CLAUDE.md` checks in `discover_projects`. - Add regression tests covering the exact issue shape (`PermissionError` on `/home/marco/rocha`) and the `_path_exists` helper directly. - Leave `CHANGELOG.md` untouched — release-please generates it from conventional commits. ## Testing - [x] Unit tests pass (`python -m pytest tests/test_learn/test_scanner.py::TestDecodePermissionError -q`) - [x] Linting passes (`ruff check`, `ruff format --check` on the two changed files) - [x] New tests added for new functionality ### Test Output ```text $ python -m pytest tests/test_learn/test_scanner.py::TestDecodePermissionError -q collected 2 items tests\test_learn\test_scanner.py .. [100%] 2 passed in 1.86s $ ruff check headroom/learn/plugins/claude.py tests/test_learn/test_scanner.py All checks passed! ``` ## Real Behavior Proof - Environment: Windows 11, Python 3.13, local dev checkout of headroom on branch off upstream/main - Exact command / steps: Simulated the issue by monkeypatching `Path.exists` to raise `PermissionError` for the colliding candidate `/home/marco/rocha`, then calling `_decode_project_path("-home-marco-rocha-butterfly-sylphina")` - Observed result: Before the fix the call propagates `PermissionError` (crash, matching the reported traceback); after the fix it returns without raising and the unreadable candidate is treated as non-existent. Both regression tests pass. - Not tested: End-to-end `headroom learn --apply` on a real Linux multi-user box with an actually unreadable `/home/<prefix>` — reproduced via the documented minimal logic instead. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| __init__.py | ||
| test_analyzer.py | ||
| test_claude_config_dir.py | ||
| test_error_classification.py | ||
| test_gemini_scanner.py | ||
| test_integration.py | ||
| test_loop_weighting.py | ||
| test_opencode_scanner.py | ||
| test_plugin_encoding.py | ||
| test_registry.py | ||
| test_rtk_loop_eval.py | ||
| test_scanner.py | ||
| test_subagent_scanning.py | ||
| test_writer.py | ||