headroom/tests/test_learn
TenderDeve 85e8699451
fix(learn): keep traceback tail in tool-error digest preview (#2596)
## Description

`_format_tool_call` in `headroom/learn/analyzer.py` built the error
preview with a head-only slice — `tc.output[:200]`. For tracebacks the
root cause (`ExceptionType: message`) is at the **tail**, so the digest
showed only `Traceback (most recent call last):` plus the first frame
and dropped the actual diagnosis. The issue reports 46% of 715 measured
errors were truncated past the 200-char head.

Closes #2590

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)

## Changes Made

- Added `_truncate_head_tail()` helper that collapses newlines and, when
over budget, keeps both the head and the tail joined by `…`.
- `_format_tool_call` now uses it for error output so the exception line
survives truncation. Short errors are returned unchanged (no marker).

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [ ] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality

### Test Output

```text
$ uv run pytest tests/test_learn/test_analyzer.py::TestDigestBuilder -q
9 passed in 1.47s

$ uv run ruff check headroom/learn/analyzer.py tests/test_learn/test_analyzer.py
All checks passed!
```

## Real Behavior Proof

- Environment: headroom @ main, Python 3.14, uv
- Exact command / steps: added a long synthetic traceback (`KeyError:
'the-actual-root-cause'` at the tail) as a failing tool call and built
the digest.
- Observed result: digest now contains both `Traceback` and `KeyError:
'the-actual-root-cause'`, separated by `…`; short errors have no `…`.
- Not tested: mypy not run locally.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective
- [x] New and existing unit tests pass locally with my changes
- [x] I did **not** edit `CHANGELOG.md`

## Additional Notes

Truncation budget stays at 200 chars (now split head/tail). mypy not run
locally; happy to adjust if CI flags anything.
2026-07-27 06:44:51 -07:00
..
__init__.py Add headroom learn: offline failure learning for coding agents 2026-02-27 21:19:03 -08:00
test_analyzer.py fix(learn): keep traceback tail in tool-error digest preview (#2596) 2026-07-27 06:44:51 -07:00
test_claude_config_dir.py fix(learn): honor CLAUDE_CONFIG_DIR when locating Claude logs and memory (#1642) 2026-07-01 23:22:16 -05:00
test_error_classification.py fix(learn): don't shadow TIMEOUT/CONNECTION with the generic RUNTIME_ERROR (#2099) 2026-07-13 10:50:54 -04:00
test_gemini_scanner.py Plugin architecture for headroom learn + live traffic flush 2026-04-09 20:30:20 -07:00
test_integration.py feat(learn): write per-project learnings to CLAUDE.local.md by default (#1115) 2026-06-22 15:05:06 -05:00
test_loop_weighting.py feat(learn): weight loops in Headroom Learn + RTK-loop eval (#1160) 2026-06-22 18:49:08 -05:00
test_opencode_scanner.py fix(learn): detect the active OpenCode database (#2587) 2026-07-26 19:50:59 -07:00
test_plugin_encoding.py Fix headroom learn crashing/no-op on Windows from missing UTF-8 encoding (#1239) 2026-06-21 10:37:37 -07:00
test_registry.py feat: add first-class OpenCode support (wrap, learn, mcp install) (#559) 2026-06-25 13:38:58 -05:00
test_rtk_loop_eval.py feat(learn): weight loops in Headroom Learn + RTK-loop eval (#1160) 2026-06-22 18:49:08 -05:00
test_scanner.py fix(learn): treat unreadable candidate paths as absent in project decode (#2446) 2026-07-22 06:16:44 -07:00
test_subagent_scanning.py fix(learn/claude): don't abort the whole scan on a null message line (#2299) 2026-07-16 14:36:11 -07:00
test_writer.py feat(learn): write per-project learnings to CLAUDE.local.md by default (#1115) 2026-06-22 15:05:06 -05:00