mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
## Description
Large Claude Code transcript files were capped by reading the first 10
MB of each append-only JSONL file. Because recent entries are appended
at the end, current-window and weighted token usage could silently omit
the newest activity.
Oversized transcripts are now read from EOF. If the capped tail begins
within a JSONL record, only that partial record is discarded. A
complete record beginning exactly at the boundary remains included.
## Type of Change
- [x] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation-only change
- [ ] Refactoring
## Changes Made
- Read the newest capped transcript bytes instead of the oldest prefix.
- Determine the tail offset using the opened file handle.
- Inspect the preceding byte to distinguish a partial record from an
exact line boundary.
- Remove partial bytes before UTF-8 decoding.
- Preserve existing behavior for transcripts below the 10 MB cap.
- Add direct session-tracking tests for aggregation and boundary
behavior.
- Add an Unreleased changelog entry.
## Testing
- [x] Added regression tests
- [x] Focused tests pass
- [x] Subscription test suite passes
- [x] Ruff checks pass
- [x] Mypy passes
- [x] Changed files pass formatting checks
- [ ] Entire repository test suite passes without baseline failures
Commands and results:
- `uv run --extra dev --frozen pytest
tests/test_subscription_session_tracking.py -q`
- `4 passed`
- Subscription-focused suite
- `53 passed`
- `uv run --extra dev --frozen ruff check .`
- Passed
- `uv run --extra dev --frozen mypy headroom --ignore-missing-imports`
- Success across 504 source files
- Changed-file Ruff formatting
- Passed
- `uv run --extra dev --frozen pytest -q`
- `9364 passed, 565 skipped, 4 failed`
- The four existing, unrelated failures are:
- `test_l2_appends_transform_label`
- `test_recovery_records_sockets_and_secures_both_backups`
- `test_dashboard_uses_cached_stats_and_lazy_history_feed_polling`
- `test_smart_crusher_log_fallback_runs_for_valid_json`
Repository-wide `ruff format --check .` identifies pre-existing
formatting drift only in the untouched
`headroom/proxy/handlers/anthropic.py`.
## Real Behavior Proof
A focused reproduction created a 10,485,787-byte transcript with a
marker entry appended after the 10 MB boundary.
Before the fix:
```text
{'file_bytes': 10485787, 'line_count': 1, 'newest_entry_present': False}
After the fix:
{'file_bytes': 10485787, 'line_count': 1, 'newest_entry_present': True}
The regression tests additionally verify that:
1. Recent token usage beyond the cap contributes to raw and weighted
totals.
2. A partial initial JSONL record is discarded.
3. A complete record exactly at the tail boundary is preserved.
4. Small transcripts retain their existing behavior.
Environment: macOS arm64, CPython 3.12.13.
Not tested: mutation of the transcript during the individual file read
by a live Claude Code process. Reads remain bounded to a single recent
snapshot.
## Review Readiness
- [x] I have performed a self-review before requesting human review.
- [x] This PR is ready for human review.
## Checklist
- [x] The change follows existing project style and error-handling
conventions.
- [x] Tests cover the reported failure and relevant boundary cases.
- [x] The 10 MB memory/read cap remains enforced.
- [x] No unrelated files or formatting changes are included.
- [x] No temporary logging or debug code remains.
- [x] The changelog has been updated.
## Additional Notes
The four full-suite failures listed above occur outside the modified
subscription code and are unrelated to this PR. All tests covering
transcript reading and subscription tracking pass.
---------
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
89 lines
2.9 KiB
Python
89 lines
2.9 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from headroom.subscription import session_tracking
|
|
|
|
|
|
def test_compute_window_tokens_reads_recent_entries_from_large_transcript(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
timestamp = "2026-01-01T00:00:00Z"
|
|
recent_entry = {
|
|
"timestamp": timestamp,
|
|
"message": {
|
|
"model": "claude-opus-4-1",
|
|
"usage": {"input_tokens": 11, "output_tokens": 7},
|
|
},
|
|
}
|
|
recent_line = json.dumps(recent_entry).encode() + b"\n"
|
|
max_file_bytes = len(recent_line) + 8
|
|
transcript = tmp_path / "session.jsonl"
|
|
transcript.write_bytes(b"x" * max_file_bytes + b"\n" + recent_line)
|
|
|
|
monkeypatch.setattr(session_tracking, "_MAX_FILE_BYTES", max_file_bytes)
|
|
monkeypatch.setattr(session_tracking, "find_transcript_files", lambda: [transcript])
|
|
|
|
entry_ts = datetime.fromisoformat(timestamp.replace("Z", "+00:00")).timestamp()
|
|
tokens = session_tracking.compute_window_tokens(entry_ts - 1, entry_ts + 1)
|
|
|
|
assert tokens.input == 11
|
|
assert tokens.output == 7
|
|
assert tokens.weighted_token_equivalent == 36.0
|
|
assert tokens.by_model == {
|
|
"claude-opus-4-1": {
|
|
"input": 11,
|
|
"output": 7,
|
|
"cache_reads": 0,
|
|
"cache_writes_5m": 0,
|
|
"cache_writes_1h": 0,
|
|
"cache_writes_total": 0,
|
|
}
|
|
}
|
|
|
|
|
|
def test_read_transcript_lines_discards_partial_initial_line(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
complete_line = b'{"marker":"recent"}\n'
|
|
max_file_bytes = len(complete_line) + 5
|
|
transcript = tmp_path / "session.jsonl"
|
|
transcript.write_bytes(b"partial-old-entry\n" + complete_line)
|
|
monkeypatch.setattr(session_tracking, "_MAX_FILE_BYTES", max_file_bytes)
|
|
|
|
assert session_tracking._read_transcript_lines(transcript) == ['{"marker":"recent"}']
|
|
|
|
|
|
def test_read_transcript_lines_preserves_line_at_tail_boundary(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
tail = b'{"marker":"first"}\n{"marker":"second"}\n'
|
|
transcript = tmp_path / "session.jsonl"
|
|
transcript.write_bytes(b'{"marker":"old"}\n' + tail)
|
|
monkeypatch.setattr(session_tracking, "_MAX_FILE_BYTES", len(tail))
|
|
|
|
assert session_tracking._read_transcript_lines(transcript) == [
|
|
'{"marker":"first"}',
|
|
'{"marker":"second"}',
|
|
]
|
|
|
|
|
|
def test_read_transcript_lines_preserves_small_transcript(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
transcript = tmp_path / "session.jsonl"
|
|
transcript.write_text('{"marker":"first"}\n\n{"marker":"second"}\n')
|
|
monkeypatch.setattr(session_tracking, "_MAX_FILE_BYTES", 1024)
|
|
|
|
assert session_tracking._read_transcript_lines(transcript) == [
|
|
'{"marker":"first"}',
|
|
'{"marker":"second"}',
|
|
]
|