Skip to content

fix(hermes): price subscription-included sessions - #1113

Merged
ryoppippi merged 1 commit into
ccusage:mainfrom
liuzemei:fix/hermes-subscription-zero-cost
May 21, 2026
Merged

ryoppippi merged 1 commit into
ccusage:mainfrom
liuzemei:fix/hermes-subscription-zero-cost

Conversation

@liuzemei

@liuzemei liuzemei commented May 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

calculate_hermes_cost in rust/crates/ccusage/src/adapter/hermes.rs short-circuits on any Some(cost) read from Hermes' state.db, so sessions where Hermes wrote estimated_cost_usd = 0.0 (subscription-included billing, e.g. ChatGPT Plus/Pro via the codex backend) report $0 even when token usage is non-zero. The codex adapter doesn't have this short-circuit and prices the same gpt-5.5 model correctly, so on the same machine ccusage codex daily shows a real cost while ccusage hermes daily shows $0.00.

This change only short-circuits on a strictly positive recorded cost and falls through to pricing-based calculation otherwise, matching how the codex adapter handles the same models. A recorded positive cost is still trusted as-is.

Closes #1112.

Changes

  • rust/crates/ccusage/src/adapter/hermes.rs
    • calculate_hermes_cost: only return early when entry.cost_usd > 0.0; otherwise fall through to the existing pricing-based candidate loop.
    • Drop the redundant cost >= 0.0 check inside the candidate loop (the subsequent cost > 0.0 already implies it).
    • Add two regression tests:
      • recorded_zero_cost_falls_back_to_pricing_calculation — reproduces the bug with real-world numbers (gpt-5.5, Some(0.0), non-zero tokens) and asserts cost > 0.
      • recorded_positive_cost_is_trusted — guards against accidentally pricing sessions where Hermes already recorded a real cost.

Test plan

  • cargo test — 219/219 pass, including the two new tests
  • rustfmt --check clean

Summary by cubic

Fixes Hermes cost calculation so subscription-included sessions with a recorded 0.0 cost are priced from token usage. Only trust a recorded cost when it’s strictly positive, matching the codex adapter.

  • Bug Fixes
    • In calculate_hermes_cost, early-return only when cost_usd > 0.0; otherwise compute via pricing.
    • Remove redundant cost >= 0.0 check.
    • Add regression tests to ensure fallback pricing for 0.0 costs and trusting positive recorded costs.

Written for commit 2432620. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

Bug Fixes

  • Improved reliability of cost reporting by adding stricter validation for recorded costs, with automatic fallback to alternative pricing calculations when recorded values are invalid.

Review Change Stack

calculate_hermes_cost short-circuits on any Some(cost) read from
state.db, so sessions where Hermes wrote estimated_cost_usd = 0.0
(subscription_included billing, e.g. ChatGPT Plus/Pro via the codex
backend) report $0 even when token usage is non-zero. Only short-circuit
on strictly positive recorded cost; fall through to pricing-based
calculation otherwise, matching how the codex adapter handles the same
models.

Closes ccusage#1112
@github-actions

Copy link
Copy Markdown
Contributor

This PR was auto-closed. Only contributors approved with lgtm can open PRs. Open an issue first.

Maintainers review auto-closed issues and reopen worthwhile ones. Issues that do not meet the quality bar in CONTRIBUTING.md may not be reopened or receive a reply.

If a maintainer replies lgtmi, your future issues will stay open. If a maintainer replies lgtm, your future issues and PRs will stay open.

See CONTRIBUTING.md.

@github-actions github-actions Bot closed this May 21, 2026
@coderabbitai

coderabbitai Bot commented May 21, 2026 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0e716c56-317e-451a-9202-a155905b0f15

📥 Commits

Reviewing files that changed from the base of the PR and between 1e1e9b3 and 2432620.

📒 Files selected for processing (1)
  • rust/crates/ccusage/src/adapter/hermes.rs

📝 Walkthrough

Walkthrough

The adapter fixes a bug where Hermes-recorded zero costs (used for subscription-based billing) were returned without fallback to pricing calculation. The code now filters recorded costs to accept only strictly positive values, rejects zero, and falls back to pricing-token calculation when needed. Pricing-derived costs now require strictly positive finite values.

Changes

Hermes Cost Fallback Logic

Layer / File(s) Summary
Cost calculation fallback logic
rust/crates/ccusage/src/adapter/hermes.rs
calculate_hermes_cost now rejects recorded zero costs and falls back to pricing-token calculation. The pricing-based return condition was tightened from cost >= 0.0 to cost.is_finite() && cost > 0.0.
Cost handling test coverage
rust/crates/ccusage/src/adapter/hermes.rs
Two new unit tests validate that recorded Some(0.0) cost results in pricing-based calculation and that recorded positive cost is returned unchanged.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 Zero costs need not persist,
When Hermes records a pricing mist—
Back to pricing we gently fall,
Fair costs for tokens one and all! 💰

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ryoppippi ryoppippi reopened this May 21, 2026
@ryoppippi

ryoppippi commented May 21, 2026 •

Copy link
Copy Markdown
Member

@coderabbitai review it

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get the PR diff
gh pr diff 1113 --repo ryoppippi/ccusage

# Also get the full file content
cat rust/crates/ccusage/src/adapter/hermes.rs

Length of output: 26906


@ryoppippi: Here's the review for #1113.


Review Summary

Overall: ✅ Looks good — the fix is correct, minimal, and well-motivated.


Core logic change

// Before — short-circuits on any recorded cost, including 0.0
if let Some(cost) = entry.cost_usd {
    return cost;
}

// After — only short-circuits on a strictly positive recorded cost
if let Some(cost) = entry.cost_usd.filter(|cost| *cost > 0.0) {
    return cost;
}

The one-liner .filter(|cost| *cost > 0.0) is idiomatic Rust and exactly the right fix. A few edge-case notes that all check out:

  • Negative stored costs: read_non_negative_f64 already clamps them to 0.0 via .max(0.0), so they arrive as Some(0.0) and correctly fall through with the new filter.
  • NaN: NaN > 0.0 is false, so NaN falls through to pricing — correct.
  • Infinity: read_f64 guards with .is_finite(), so infinity cannot reach cost_usd at all.

The secondary cleanup removing the dead cost >= 0.0 guard in the pricing loop is valid — cost > 0.0 already implies it.


Tests

Both new tests cover the right invariants:

Test What it verifies
recorded_zero_cost_falls_back_to_pricing_calculation Some(0.0) → pricing-based cost > 0
recorded_positive_cost_is_trusted Some(0.42) → returned as-is

Minor nit — float equality in recorded_positive_cost_is_trusted:

assert_eq!(calculate_hermes_cost(&entry, &pricing), 0.42);

This works correctly here because the value is stored and returned with no arithmetic, so the bits are preserved exactly. It's not wrong, but if this pattern is extended to cases where arithmetic is applied, it could silently break. A small defensive alternative for future readers:

let cost = calculate_hermes_cost(&entry, &pricing);
assert!((cost - 0.42).abs() < f64::EPSILON, "expected ~0.42, got {cost}");

Not a blocker — just worth keeping in mind if the test is ever expanded.


Coverage gap (non-blocking)

There's no explicit test for cost_usd = None in calculate_hermes_cost, but the existing calculates_cost_for_hermes_frontier_models_from_embedded_pricing test already covers that path via cost_usd: None. No action needed.


The fix is correct and the PR is in good shape. 🐇

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the hermes adapter file
cat rust/crates/ccusage/src/adapter/hermes.rs

echo "---CODEX ADAPTER---"
# Read the codex adapter for comparison
cat rust/crates/ccusage/src/adapter/codex.rs 2>/dev/null || echo "Not found"

echo "---DIFF---"
git diff main...HEAD -- rust/crates/ccusage/src/adapter/hermes.rs

Length of output: 56837


@ryoppippi The fix is clean and correct. Here's the full review:


Review of #1113 — fix(hermes): price subscription-included sessions

Core logic change (calculate_hermes_cost)

The change from:

if let Some(cost) = entry.cost_usd {

to:

if let Some(cost) = entry.cost_usd.filter(|cost| *cost > 0.0) {

is the minimal, correct fix. It exactly mirrors the intent: only short-circuit when Hermes has a meaningful recorded cost. Since read_non_negative_f64 already clamps any negative DB value to 0.0 via .max(0.0), NaN and negative values can never reach this function as Some(...), so the > 0.0 guard is both sufficient and correct.

The removal of the redundant cost >= 0.0 in the candidate loop is a clean tidy-up — cost > 0.0 already implies >= 0.0.

Tests

Both new tests are well-written:

  • recorded_zero_cost_falls_back_to_pricing_calculation uses real-world token numbers from the linked issue — good regression anchor.
  • recorded_positive_cost_is_trusted uses an exact equality assertion (assert_eq!(..., 0.42)) which is correct since the early return path has no floating-point arithmetic.

Note: both tests depend on gpt-5.5 being present in the embedded pricing table, which is already guarded by calculates_cost_for_hermes_frontier_models_from_embedded_pricing — acceptable coupling.

Minor observation: data.cost_usd vs LoadedEntry.cost divergence

For subscription sessions, to_loaded_entry now produces a LoadedEntry where:

  • cost (used for aggregation/display) = pricing-computed, non-zero ✅
  • data.cost_usd (raw Hermes value passed into UsageEntry) = Some(0.0)

If any JSON output path surfaces UsageEntry.cost_usd directly (e.g., a hermes sessions --json query), it would still show 0.0 for subscription sessions. This is a pre-existing design and not introduced by this PR, but it may be worth a follow-up to either update data.cost_usd to the computed cost or document the intentional split.

Summary

Fix correctness ✅
Edge cases (NaN, negative, None) ✅ handled upstream
Redundant check removed ✅
Tests coverage ✅ both branches covered
Pre-existing data.cost_usd inconsistency ⚠️ out of scope for this PR

The PR is good to merge. The data.cost_usd inconsistency is a separate concern worth a follow-up issue if it affects any output path.

@ryoppippi

Copy link
Copy Markdown
Member

@cubic-dev-ai review this

@cubic-dev-ai

cubic-dev-ai Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this

@ryoppippi I have started the AI code review. It will take a few minutes to complete.

@ryoppippi ryoppippi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@ryoppippi
ryoppippi merged commit 95d9a9f into ccusage:main May 21, 2026
18 checks passed
@ryoppippi ryoppippi added the triage:resolved Resolved by a later change or current implementation. label Aug 31, 2026
@ryoppippi

Copy link
Copy Markdown
Member

Historical audit: this pull request was auto-closed by the legacy contributor gate. That closure did not assess technical importance.

Audit result: resolved. A later merged change or the current main implementation covers this request. This PR is kept for history and does not need to be revived.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

triage:resolved Resolved by a later change or current implementation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ccusage hermes reports $0 cost when Hermes session uses subscription billing

2 participants