fix(hooks): correct cost-tracker pricing table and filter harness noise from session summaries - #2772
Conversation
…e from session summaries cost-tracker.js: RATE_TABLE priced all Opus models at the legacy $15/$75 tier and routed Fable/Mythos 5 to Sonnet rates, overstating Opus 5 sessions ~3x and understating Fable ~3.3x in costs.jsonl. Adds fable ($10/$50) and current opus ($5/$25) tiers, keeps Opus 4.0/4.1/3 on the legacy tier, updates haiku to 4.5 pricing ($1/$5). session-end.js: extractSessionSummary included local-command echoes (<local-command-caveat>, <command-name>, <local-command-stdout>), system reminders, tool_result carrier turns and isMeta entries in the Tasks list, so SessionStart reloaded noise instead of user asks. Adds a noise filter. Both test suites pass (10/10 cost-tracker, 1/1 session-end). Co-Authored-By: Claude Fable 5 <[email protected]>
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe PR updates model pricing and matching in the cost tracker. It also filters tool results, harness noise, and metadata from session summary user messages. ChangesModel pricing
Session summary filtering
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/hooks/cost-tracker.js`:
- Around line 73-88: Expand the cost-tracker tests with table-driven cases
covering Fable, Mythos, Haiku 4.5, current Opus, and legacy Opus pricing tiers,
asserting both selected rates and calculated costs. Include
claude-opus-4-20250514 to verify it routes to the intended legacy/current tier,
and ensure the JavaScript test coverage remains at least 80%.
- Around line 88-89: Update the model-routing logic in the cost-tracker matcher
so the canonical Opus 4.0 identifier pattern used by the integration test,
including claude-opus-4-20250514, resolves to RATE_TABLE.opusLegacy before the
generic opus branch. Preserve the existing routing for other model identifiers.
In `@scripts/hooks/session-end.js`:
- Around line 46-52: Add regression tests covering the filtering branches in the
session-end message processing: array content containing tool_result, each
harness-noise tag, and entries marked isMeta. For every filter, verify both that
matching entries are excluded and that legitimate user messages remain; assert
filtered entries do not increase totalMessages.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 36cc17ca-54a3-469f-92aa-cf37d33d0738
📒 Files selected for processing (2)
scripts/hooks/cost-tracker.jsscripts/hooks/session-end.js
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
- GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (18)
**/*.{js,ts,jsx,tsx,py,java,cs,go,rb,php,scala,kt}
📄 CodeRabbit inference engine (.cursor/rules/common-coding-style.md)
**/*.{js,ts,jsx,tsx,py,java,cs,go,rb,php,scala,kt}: Always create new objects, never mutate existing ones. Use immutable patterns to prevent hidden side effects and enable safe concurrency
Organize code into many small files (200-400 lines typical, 800 lines max) organized by feature/domain rather than by type
Always handle errors explicitly at every level and never silently swallow errors
Always validate all user input before processing at system boundaries
Use schema-based validation where available
Fail fast with clear error messages when validation fails
Never trust external data (API responses, user input, file content)
Ensure code is readable and well-named
Keep functions small (less than 50 lines)
Keep files focused (less than 800 lines)
Avoid deep nesting (more than 4 levels)
Do not use hardcoded values; use constants or configuration instead
Files:
scripts/hooks/session-end.jsscripts/hooks/cost-tracker.js
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php,swift,kt,rs,c,cpp,h,hpp}
📄 CodeRabbit inference engine (.cursor/rules/common-security.md)
No hardcoded secrets (API keys, passwords, tokens) - validate before any commit
Files:
scripts/hooks/session-end.jsscripts/hooks/cost-tracker.js
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php}
📄 CodeRabbit inference engine (.cursor/rules/common-security.md)
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php}: All user inputs must be validated
Enable CSRF protection on all state-changing endpoints
Verify authentication and authorization for all protected endpoints
Implement rate limiting on all endpoints to prevent abuse
Ensure error messages do not leak sensitive data in responses
Files:
scripts/hooks/session-end.jsscripts/hooks/cost-tracker.js
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php,sql}
📄 CodeRabbit inference engine (.cursor/rules/common-security.md)
Use parameterized queries to prevent SQL injection
Files:
scripts/hooks/session-end.jsscripts/hooks/cost-tracker.js
**/*.{js,ts,jsx,tsx,html,php,java,cs,rb,go}
📄 CodeRabbit inference engine (.cursor/rules/common-security.md)
Implement XSS prevention by sanitizing HTML output
Files:
scripts/hooks/session-end.jsscripts/hooks/cost-tracker.js
**/*.{js,ts,jsx,tsx,py,java,cs,rb,go,php,swift,kt,rs,c,cpp,h,hpp,properties,yml,yaml,json,env,config}
📄 CodeRabbit inference engine (.cursor/rules/common-security.md)
NEVER hardcode secrets in source code - ALWAYS use environment variables or a secret manager
Files:
scripts/hooks/session-end.jsscripts/hooks/cost-tracker.js
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/typescript-coding-style.md)
**/*.{ts,tsx,js,jsx}: Use spread operator for immutable updates in TypeScript/JavaScript instead of direct mutation
Use async/await with try-catch for error handling in TypeScript/JavaScript
Use Zod for schema-based input validation in TypeScript/JavaScript
No console.log statements in production code; use proper logging libraries instead
**/*.{ts,tsx,js,jsx}: Auto-format JavaScript/TypeScript files using Prettier after edit
Warn aboutconsole.logstatements in edited files
Check all modified files forconsole.logstatements before session ends
**/*.{ts,tsx,js,jsx}: Use the ApiResponse interface pattern with generic type parameter:interface ApiResponse<T> { success: boolean; data?: T; error?: string; meta?: { total: number; page: number; limit: number; } }
Implement custom React hooks following the pattern: export a named function with use prefix, generic type parameters, and proper useEffect cleanup for side effects
**/*.{ts,tsx,js,jsx}: Never hardcode secrets; always use environment variables for sensitive credentials like API keys
Throw an error when required environment variables are not configured to fail fast and ensure security prerequisites are metUse Playwright as the E2E testing framework for critical user flows in TypeScript/JavaScript
Files:
scripts/hooks/session-end.jsscripts/hooks/cost-tracker.js
{package.json,*.config.js,scripts/**/*.js}
📄 CodeRabbit inference engine (CLAUDE.md)
Package manager detection should support npm, pnpm, yarn, and bun, with configuration via CLAUDE_PACKAGE_MANAGER environment variable or project config.
Files:
scripts/hooks/session-end.jsscripts/hooks/cost-tracker.js
scripts/**/*.js
📄 CodeRabbit inference engine (CLAUDE.md)
Ensure cross-platform support for Windows, macOS, and Linux via Node.js scripts in the scripts/ directory.
Files:
scripts/hooks/session-end.jsscripts/hooks/cost-tracker.js
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,ts,jsx,tsx}: Always create new objects and never mutate in place; return new copies instead
Keep files between 200–400 lines typical, with a maximum of 800 lines
Extract helpers when a file exceeds 200 lines
Handle errors explicitly at every level; never swallow errors silently
Validate all user input before processing; use schema-based validation where available
Never trust external data (API responses, file content, query params); always validate
All user inputs must be validated and sanitized
Error messages must be scrubbed of sensitive internals
Use readable, well-named identifiers in all code
Keep functions under 50 lines
Keep files under 800 lines
Avoid nesting deeper than 4 levels
Implement comprehensive error handling in all code
Do not hardcode values; use constants or environment configuration instead
Do not use in-place mutation; always return new objects or state
Files:
scripts/hooks/session-end.jsscripts/hooks/cost-tracker.js
**/*.{js,ts,jsx,tsx,json,env*}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Do not hardcode secrets, API keys, passwords, or tokens
Files:
scripts/hooks/session-end.jsscripts/hooks/cost-tracker.js
**/*.{js,ts}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,ts}: Use parameterized queries for all database writes (no string interpolation)
Auth/authz must be checked server-side for every sensitive path
Rate limiting must be applied to all public endpoints
Files:
scripts/hooks/session-end.jsscripts/hooks/cost-tracker.js
**/*.{jsx,tsx,js,ts}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
HTML output must be sanitized where applicable
Files:
scripts/hooks/session-end.jsscripts/hooks/cost-tracker.js
**/*.{js,ts,env*}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Required environment variables must be validated at startup
Files:
scripts/hooks/session-end.jsscripts/hooks/cost-tracker.js
**/*.{js,jsx,ts,tsx,py,java,kt,go,rs,cpp,c,h,cs,rb,php}
📄 CodeRabbit inference engine (AGENTS.md)
Test-Driven — Write tests before implementation, 80%+ coverage required
Files:
scripts/hooks/session-end.jsscripts/hooks/cost-tracker.js
**/*
📄 CodeRabbit inference engine (AGENTS.md)
**/*: Security-First — Never compromise on security; validate all inputs
Immutability — Always create new objects, never mutate existing ones
- No hardcoded secrets (API keys, passwords, tokens)
- All user inputs validated
- Authentication/authorization verified
- Error messages don't leak sensitive data
Immutability (CRITICAL): Always create new objects, never mutate. Return new copies with changes applied.
Error handling: Handle errors at every level. Provide user-friendly messages in UI code. Log detailed context server-side. Never silently swallow errors.
Input validation: Validate all user input at system boundaries. Use schema-based validation. Fail fast with clear messages. Never trust external data.
Minimum coverage: 80%
TDD workflow (mandatory):
Commit format:<type>: <description>— Types: feat, fix, refactor, docs, test, chore, perf, ci
API response format: Consistent envelope with success indicator, data payload, error message, and pagination metadata.
Files:
scripts/hooks/session-end.jsscripts/hooks/cost-tracker.js
**/*.{sql,js,jsx,ts,tsx,py,java,kt,go,rs,php}
📄 CodeRabbit inference engine (AGENTS.md)
- SQL injection prevention (parameterized queries)
Files:
scripts/hooks/session-end.jsscripts/hooks/cost-tracker.js
{scripts,bin}/**
⚙️ CodeRabbit configuration file
{scripts,bin}/**: Focus on command injection, unsafe subprocess usage, path traversal, SSRF, secret exposure, and missing tests for new CLI behavior.
Files:
scripts/hooks/session-end.jsscripts/hooks/cost-tracker.js
| // Current-generation list prices: Fable/Mythos 5 $10/$50, Opus 5 and | ||
| // Opus 4.5-4.8 $5/$25, Sonnet 5/4.6 $3/$15, Haiku 4.5 $1/$5. Opus 4.0/4.1 | ||
| // and Opus 3 stay on the legacy $15/$75 tier. | ||
| const RATE_TABLE = { | ||
| haiku: { in: 0.80, out: 4.0, cacheWrite: 1.00, cacheRead: 0.08 }, | ||
| sonnet: { in: 3.00, out: 15.0, cacheWrite: 3.75, cacheRead: 0.30 }, | ||
| opus: { in: 15.00, out: 75.0, cacheWrite: 18.75, cacheRead: 1.50 } | ||
| haiku: { in: 1.00, out: 5.0, cacheWrite: 1.25, cacheRead: 0.10 }, | ||
| sonnet: { in: 3.00, out: 15.0, cacheWrite: 3.75, cacheRead: 0.30 }, | ||
| opus: { in: 5.00, out: 25.0, cacheWrite: 6.25, cacheRead: 0.50 }, | ||
| opusLegacy: { in: 15.00, out: 75.0, cacheWrite: 18.75, cacheRead: 1.50 }, | ||
| fable: { in: 10.00, out: 50.0, cacheWrite: 12.50, cacheRead: 1.00 } | ||
| }; | ||
|
|
||
| function getRates(model) { | ||
| const m = String(model || '').toLowerCase(); | ||
| if (m.includes('fable') || m.includes('mythos')) return RATE_TABLE.fable; | ||
| if (m.includes('haiku')) return RATE_TABLE.haiku; | ||
| if (m.includes('opus-4-1') || m.includes('opus-4-0') || m.includes('3-opus')) return RATE_TABLE.opusLegacy; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Add regression tests for every new pricing branch.
The supplied cost-tracker test checks only that one estimate is positive. It does not assert exact rates for Fable, Mythos, Haiku 4.5, current Opus, or legacy Opus. Add table-driven cases that verify tier selection and computed cost. Include claude-opus-4-20250514 to catch the routing defect above.
As per coding guidelines, JavaScript changes require tests before implementation and at least 80% coverage.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/hooks/cost-tracker.js` around lines 73 - 88, Expand the cost-tracker
tests with table-driven cases covering Fable, Mythos, Haiku 4.5, current Opus,
and legacy Opus pricing tiers, asserting both selected rates and calculated
costs. Include claude-opus-4-20250514 to verify it routes to the intended
legacy/current tier, and ensure the JavaScript test coverage remains at least
80%.
Source: Coding guidelines
| if (m.includes('opus-4-1') || m.includes('opus-4-0') || m.includes('3-opus')) return RATE_TABLE.opusLegacy; | ||
| if (m.includes('opus')) return RATE_TABLE.opus; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Route the canonical Opus 4.0 identifier to the legacy tier.
The supplied integration test uses claude-opus-4-20250514 in tests/hooks/cost-tracker.test.js Lines 75-130. Line 88 does not match this identifier, so Line 89 applies RATE_TABLE.opus at $5/$25 instead of RATE_TABLE.opusLegacy at $15/$75. Match the canonical Opus 4.0 form or parse the model version explicitly.
Suggested matcher fix
- if (m.includes('opus-4-1') || m.includes('opus-4-0') || m.includes('3-opus')) return RATE_TABLE.opusLegacy;
+ const isLegacyOpus4 =
+ m.includes('opus-4-0') ||
+ m.includes('opus-4-1') ||
+ /(?:^|-)opus-4-\d{8}(?:-|$)/.test(m);
+ if (m.includes('3-opus') || isLegacyOpus4) return RATE_TABLE.opusLegacy;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (m.includes('opus-4-1') || m.includes('opus-4-0') || m.includes('3-opus')) return RATE_TABLE.opusLegacy; | |
| if (m.includes('opus')) return RATE_TABLE.opus; | |
| const isLegacyOpus4 = | |
| m.includes('opus-4-0') || | |
| m.includes('opus-4-1') || | |
| /(?:^|-)opus-4-\d{8}(?:-|$)/.test(m); | |
| if (m.includes('3-opus') || isLegacyOpus4) return RATE_TABLE.opusLegacy; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/hooks/cost-tracker.js` around lines 88 - 89, Update the model-routing
logic in the cost-tracker matcher so the canonical Opus 4.0 identifier pattern
used by the integration test, including claude-opus-4-20250514, resolves to
RATE_TABLE.opusLegacy before the generic opus branch. Preserve the existing
routing for other model identifiers.
| // Skip tool_result carrier turns — they are not user asks. | ||
| const isToolResult = Array.isArray(rawContent) && rawContent.some(c => c && c.type === 'tool_result'); | ||
| const text = typeof rawContent === 'string' ? rawContent : Array.isArray(rawContent) ? rawContent.map(c => (c && c.text) || '').join(' ') : ''; | ||
| const cleaned = stripAnsi(text).trim(); | ||
| if (cleaned) { | ||
| // Skip harness noise: local command echoes, caveats, system reminders. | ||
| const isNoise = /^<(local-command-caveat|local-command-stdout|command-name|command-message|command-args|system-reminder|task-notification)/i.test(cleaned); | ||
| if (cleaned && !isToolResult && !isNoise && !entry.isMeta) { |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Add regression tests for every new filter.
The supplied test covers array content parsing, but it does not cover tool_result, each harness tag, or isMeta. Add positive and negative cases for these branches. Verify that legitimate user messages remain and that filtered entries do not increase totalMessages.
As per coding guidelines, “Test-Driven — Write tests before implementation, 80%+ coverage required.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/hooks/session-end.js` around lines 46 - 52, Add regression tests
covering the filtering branches in the session-end message processing: array
content containing tool_result, each harness-noise tag, and entries marked
isMeta. For every filter, verify both that matching entries are excluded and
that legitimate user messages remain; assert filtered entries do not increase
totalMessages.
Source: Coding guidelines
|
| const m = String(model || '').toLowerCase(); | ||
| if (m.includes('fable') || m.includes('mythos')) return RATE_TABLE.fable; | ||
| if (m.includes('haiku')) return RATE_TABLE.haiku; | ||
| if (m.includes('opus-4-1') || m.includes('opus-4-0') || m.includes('3-opus')) return RATE_TABLE.opusLegacy; |
There was a problem hiding this comment.
Dated Opus 4.0 model uses current rates
When the authoritative harness-cost cache is absent or stale, claude-opus-4-20250514 does not match the legacy predicates and falls through to the generic Opus rate. The transcript fallback therefore records $30 for 1M input plus 1M output tokens instead of the $90 legacy Opus 4.0 estimate. Classify this dated Opus 4.0 identifier as opusLegacy before the generic opus branch and add a regression case for the fallback path.
| // Skip tool_result carrier turns — they are not user asks. | ||
| const isToolResult = Array.isArray(rawContent) && rawContent.some(c => c && c.type === 'tool_result'); | ||
| const text = typeof rawContent === 'string' ? rawContent : Array.isArray(rawContent) ? rawContent.map(c => (c && c.text) || '').join(' ') : ''; | ||
| const cleaned = stripAnsi(text).trim(); | ||
| if (cleaned) { | ||
| // Skip harness noise: local command echoes, caveats, system reminders. | ||
| const isNoise = /^<(local-command-caveat|local-command-stdout|command-name|command-message|command-args|system-reminder|task-notification)/i.test(cleaned); | ||
| if (cleaned && !isToolResult && !isNoise && !entry.isMeta) { |
There was a problem hiding this comment.
Mixed user turns discard genuine task text
A user content array containing both a text block and a tool_result block is rejected wholesale because isToolResult is true when any block is a tool result. The same text persists as a Task in a text-only turn but disappears in the mixed-block case, so legitimate follow-up requests are lost from cross-session context. Exclude only tool_result blocks when extracting text instead of skipping the whole turn.
| if (m.includes('fable') || m.includes('mythos')) return RATE_TABLE.fable; | ||
| if (m.includes('haiku')) return RATE_TABLE.haiku; |
There was a problem hiding this comment.
New classifications lack regression coverage
The changed rate mappings and transcript exclusions lack assertions for exact legacy Opus costs and mixed tool-result, noise, and meta content, allowing these classifications to regress without failing the test suite.
Rule Used: Treat CLI inputs, URLs, file paths, and subprocess... (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Summary
Two fixes to runtime hooks, found while auditing a fresh 2.2.0 plugin install:
1.
scripts/hooks/cost-tracker.js— stale pricing tableRATE_TABLE.opuswas $15/$75 per MTok (Opus 3/4.0/4.1 legacy pricing). Current Opus 5 and Opus 4.5–4.8 are $5/$25, soestimated_cost_usdin~/.claude/metrics/costs.jsonloverstated Opus sessions ~3x (verified against live rows).claude-fable-5/claude-mythos-5matched no branch and fell through to Sonnet rates ($3/$15 instead of $10/$50), understating ~3.3x.opusLegacybranch.2.
scripts/hooks/session-end.js— harness noise in session summariesextractSessionSummarytreated local-command echoes as user asks, so the cross-session summary's Tasks list filled with<local-command-caveat>,<command-name>,<local-command-stdout>fragments — and SessionStart then re-injected that noise as context. Now filters those tags,<system-reminder>/<task-notification>blocks,tool_resultcarrier turns, andisMetaentries. Verified against a live transcript: Tasks now contains only real user messages.Testing
node tests/hooks/cost-tracker.test.js— 10/10 passnode tests/hooks/session-end.test.js— 1/1 pass🤖 Generated with Claude Code