A local-first usage report still needs production failure boundaries. Claude Code and Codex session logs are append-only JSONL in the normal case, but they can contain embedded tool payloads, pasted images, partial writes, duplicate events, and files that change while a scan is running.
Agent Island v1.7.1 addresses two incidents in that pipeline: pathological lines can no longer buffer without a ceiling, and an in-flight scan older than ten minutes no longer blocks refreshes for the rest of the process lifetime.
Treat memory and liveness as separate bounds
A line-size limit protects memory. A scan-age limit protects liveness. They solve different problems and should not share one catch-all timeout.
for each session file:
stream lines with a platform-specific byte policy
parse only usage-bearing assistant events
deduplicate stable event identities
on refresh:
if scan is active and younger than wedge threshold:
keep the current scan
else:
start a fresh scan off the UI thread
Choose the line policy from the data contract
The macOS Claude reader uses a 64 MiB backstop. It is a memory ceiling, not a normal filter. Claude usage can sit on an assistant line that also carries a large tool payload, so discarding every line above a small threshold would make local totals diverge from the source log.
The Windows reader uses a stricter rule: it skips a line above one million characters before JSON parsing. This prevents a multi-megabyte embedded payload from inflating peak memory. The difference is explicit and platform-specific; it should not be described as one identical parser.
In both cases, malformed and partial lines fail closed for that record. An actively written transcript can race the reader, so the next fingerprint change causes a later poll to parse it again.
Discard structure that cannot contain usage
After a line passes the byte policy, the reader still should not deserialize it into a broad application model. The released Claude reader accepts only rows with:
- top-level
type == "assistant"; - a
message.usageobject and non-placeholder model; - a parseable timestamp;
- at least one non-zero token field.
It deduplicates Claude events with messageId:requestId when both identifiers are available. A cache keyed by path, modification time, and size avoids re-reading unchanged files on each refresh. Those rules reduce normal work; they do not replace the pathological-line ceiling.
Let a stale in-flight gate expire
A common refresh guard is “if loading, return.” It prevents overlapping scans, but it also turns one task that never commits into a permanent freeze. The UI can keep showing yesterday's data while every scheduled refresh exits at the same boolean.
The v1.7.1 macOS store tracks Claude and Codex scans separately. If one provider's scan is still active but began more than 600 seconds ago, the next refresh is allowed to start a replacement. A slow Claude scan therefore does not block a healthy Codex scan. Windows applies the same ten-minute escape to its shared scan latch.
The threshold is not a claim that ten minutes is a normal scan duration. It is a recovery boundary chosen far above the expected path. Instrument the actual scan distribution before tightening it.
Commit only completed summaries
Replacement scans create a second problem: an older task may finish after the replacement and overwrite fresher data. A robust store should associate each scan with a generation token and commit only the newest generation. The current release restores liveness with an age gate; generation-aware commit ordering is the stronger general design for any scanner that can overlap after recovery.
Keep parsing and summarization off the UI thread, then update published state in one commit. Do not expose a half-updated provider summary.
Failure tests worth keeping
- A normal usage event survives the line policy and contributes once.
- A duplicate event does not inflate totals.
- A partial final line does not crash the scan.
- A pathological line remains below the intended memory ceiling.
- An unchanged file is served from the parse cache.
- A stuck scan stops blocking refresh after the documented threshold.
- One provider's slow scan does not freeze another provider on platforms with per-provider gates.
- An older recovered task cannot overwrite a newer committed generation.
Keep the measurement claim narrow
A local token ledger reconstructs usage events from files on the user's machine. It does not prove what a subscription charged, what a user saved, or how many people use the product. Agent Island labels API value as an estimate and keeps the scan local; no session data is uploaded to an Agent Island service.
The line bounds and wedge recovery described here are in Agent Island v1.7.1. The source remains public for teams that need to audit the exact platform behavior.
