mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-18 15:54:13 +08:00
cd176504d34cca67f6760043574d2ee4ae864e7f
4 Commits
-
fix(hooks): log fail-open breadcrumb on parse/read errors in metrics bridge
coderabbitai flagged: the two `catch` blocks in `readSessionCost` silently swallowed every failure mode. A malformed `costs.jsonl` row, a permission error opening the file, or any other unexpected I/O failure would silently return zero cost — masking real problems and feeding stale or zero numbers into `ecc-context-monitor.js` (which then injects them as `additionalContext` into the live model turn). Fix two things, both fail-open-preserving: 1. **Inner JSON.parse catch** — count malformed lines and write one aggregated breadcrumb per call: [ecc-metrics-bridge] skipped N malformed line(s) in <path> Aggregating (rather than per-line) keeps a log-flooded `costs.jsonl` diagnosable without overwhelming stderr. 2. **Outer fs.readFileSync catch** — write a breadcrumb on real errors, but stay silent on `ENOENT`. The "no costs.jsonl yet" case is genuinely normal (no Stop event has fired this session) and producing noise on every PreToolUse before the first Stop would be reviewer-visible spam. All other error codes (`EACCES`, `EISDIR`, `EMFILE`, …) get: [ecc-metrics-bridge] failing open after <name> reading <path>: <msg> In both cases the function still returns the zero-cost fallback so the bridge never breaks tool execution — only the diagnosability changes. Two new regression tests in `tests/hooks/ecc-metrics-bridge.test.js`: ✓ readSessionCost writes a stderr breadcrumb when malformed lines are skipped — feeds 4 rows (2 valid, 2 malformed), asserts the last valid row still wins AND captured stderr contains "skipped 2 malformed line(s)". ✓ readSessionCost stays silent when costs.jsonl does not exist (ENOENT) — uses a fresh tmp HOME with no metrics dir, asserts zero return AND empty stderr. Test count: 16 → 18; `npm test` green; `yarn lint` clean.Jamkris ·
2026-05-18 10:03:21 +09:00 -
fix(hooks): scan full costs.jsonl when locating session row
`readSessionCost` read only the trailing 8 KiB of `~/.claude/metrics/costs.jsonl` to "avoid scanning entire file". That ceiling is the opposite-sign sibling of the double-count bug fixed in the previous commit: once a session's most recent cumulative row gets pushed past the 8 KiB window by newer rows from other sessions, the bridge silently reports `totalCost: 0`, `totalIn: 0`, `totalOut: 0` for that session — same false signal to `ecc-context-monitor.js`, same wrong number injected into the live model turn as `additionalContext`. `cost-tracker.js` has no rotation policy, so on any non-trivial workstation costs.jsonl grows past 8 KiB within minutes of normal use. For users who keep multiple concurrent sessions, this means the second-and-later sessions silently report zero almost immediately. Reproduced before this commit: $ HOME=/tmp/eccc node -e ' const fs = require("fs"); const m = require("./scripts/hooks/ecc-metrics-bridge.js"); // S1 row at file start, then 200 rows of OTHER-session noise (~16 KiB). // S1 is the row we want, but it sits past the 8 KiB tail. const s1 = `{"session_id":"S1","estimated_cost_usd":0.5,"input_tokens":500,"output_tokens":250}`; const other = `{"session_id":"OTHER","estimated_cost_usd":1,"input_tokens":100,"output_tokens":50}`; fs.mkdirSync("/tmp/eccc/.claude/metrics", { recursive: true }); fs.writeFileSync("/tmp/eccc/.claude/metrics/costs.jsonl", [s1, ...Array(200).fill(other)].join("\\n") + "\\n"); console.log(JSON.stringify(m.readSessionCost("S1")));' {"totalCost":0,"totalIn":0,"totalOut":0} Expected: `{"totalCost":0.5, "totalIn":500, "totalOut":250}` (the S1 row that exists in the file). Actual: zero — the row is past the 8 KiB tail. Fix: drop the `fs.openSync` + bounded `fs.readSync` + position arithmetic in favour of `fs.readFileSync(costsPath, 'utf8')` and iterate every line. Each row is ~150 bytes; even 100k rows is ~15 MB and a single sync read on PreToolUse is in the low ms. If file rotation lands in `cost-tracker.js` later, this scan becomes proportionally cheaper. After this commit the reproduction above returns `{"totalCost":0.5, "totalIn":500, "totalOut":250}`. Regression test in `tests/hooks/ecc-metrics-bridge.test.js`: `readSessionCost finds session row beyond the old 8 KiB tail boundary`. The test asserts the costs.jsonl fixture is > 8 KiB before reading so any reintroduction of a bounded tail would re-fail the test (i.e. the assertion is the contract, not the specific number 8192). Together with the previous commit, both directions of the metrics-bridge cost-reporting bug are closed.Jamkris ·
2026-05-18 08:45:48 +09:00 -
fix(hooks): use last cumulative row for session cost in metrics bridge
`ecc-metrics-bridge.js#readSessionCost` summed the `estimated_cost_usd`, `input_tokens`, and `output_tokens` of every matching row in `~/.claude/metrics/costs.jsonl`. That breaks the documented contract of `scripts/hooks/cost-tracker.js`, which explicitly states (in its module docblock): Cumulative behavior: Stop fires per assistant response, not per session. Each row therefore represents the cumulative session total up to that point. To get per-session cost, take the last row per session_id. Summing N cumulative rows over-counts by roughly (N+1)/2 ×. For a session with 3 rows at 0.01, 0.02, 0.03 USD (true running total 0.03), the bridge today reports 0.06 USD. The over-counted value feeds `ecc-context-monitor.js`, which then trips its COST_NOTICE_USD / COST_WARNING_USD / COST_CRITICAL_USD thresholds on phantom spend AND injects the inflated number as `additionalContext` into the live model turn — so the agent itself is told a wrong cost. Reproduced on `main` before this commit: $ cat > /tmp/eccc/.claude/metrics/costs.jsonl <<EOF {"session_id":"S1","estimated_cost_usd":0.01,"input_tokens":333,"output_tokens":166} {"session_id":"S1","estimated_cost_usd":0.02,"input_tokens":666,"output_tokens":333} {"session_id":"S1","estimated_cost_usd":0.03,"input_tokens":1000,"output_tokens":500} EOF $ HOME=/tmp/eccc node -e 'const m = require("./scripts/hooks/ecc-metrics-bridge.js"); \ console.log(JSON.stringify(m.readSessionCost("S1")))' {"totalCost":0.06,"totalIn":1999,"totalOut":999} Expected: `{"totalCost":0.03,"totalIn":1000,"totalOut":500}` (the last cumulative row). Actual: 2× over-count. Fix: replace `+=` with `=` in the matching branch so the assigned values reflect the most recent row encountered. The iteration order is file order, which is also event time order, so the last assignment wins — exactly the contract cost-tracker writes against. After this commit the reproduction above returns `{"totalCost":0.03,"totalIn":1000,"totalOut":500}`. Regression test in `tests/hooks/ecc-metrics-bridge.test.js`: `readSessionCost returns the LAST cumulative row, not the sum (cost-tracker contract)`. The existing `readSessionCost does not include unrelated default-session rows` test happened to pass even with the bug because it only had one target-session row — single-row sessions are coincidentally correct under both formulas. The new test uses three rows so the two formulas diverge. A second issue in the same function — the 8 KiB tail-only read silently drops older rows once a session's recent cumulative totals scroll past that window — is fixed in the next commit.Jamkris ·
2026-05-18 08:45:48 +09:00 -
feat: add ECC statusline observability hooks
Salvages the useful statusline/context monitor work from stale PR #1504 while preserving the current continuous-learning hook runner wiring. Adds the metrics bridge, context monitor, statusline script, shared cost/session bridge utilities, and tests. Fixes the reviewed false loop-detection hash collision for non-file tools, avoids default-session cost inflation, sanitizes statusline task lookup, and records hook payload session IDs in cost-tracker.
Affaan Mustafa ·
2026-05-11 23:44:06 -04:00