Repository navigation
Conversation
…costs Subscription users on Claude Team / Max / Pro pay a flat monthly fee, so token-calculated dollar amounts are misleading. There is no usable signal in local JSONL files to detect subscription accounts automatically, so this adds an explicit opt-in: set `"billing": "subscription"` in `ccusage.json` to force all cost output to $0.00 while keeping token counts intact. Closes ccusage#1280 (relates to ccusage#658, ccusage#1112, ccusage#4) Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
📝 WalkthroughWalkthroughThe PR adds a ChangesSubscription Billing Feature
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
rust/crates/ccusage/src/adapter/opencode/parser.rs (1)
98-127:⚠️ Potential issue | 🟠 Major | ⚡ Quick winSubscription billing is not enforced in OpenCode cost calculation.
Line 103 ignores
mode(_mode), so subscription can still emit positive costs via storedcost_usd(Lines 106-108). That breaks thebilling: "subscription"zero-cost guarantee.Proposed fix
fn calculate_open_code_cost( model: &str, provider: &str, usage: TokenUsageRaw, cost_usd: Option<f64>, - _mode: CostMode, + mode: CostMode, pricing: Option<&PricingMap>, ) -> f64 { + if mode == CostMode::Subscription { + return 0.0; + } if let Some(cost) = cost_usd.filter(|cost| *cost > 0.0) { return cost; } for candidate in open_code_model_candidates(model, provider) { let cost =🤖 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 `@rust/crates/ccusage/src/adapter/opencode/parser.rs` around lines 98 - 127, calculate_open_code_cost currently ignores the CostMode by naming the parameter _mode and immediately returning stored cost_usd, which allows subscription-billed calls to produce non-zero costs; change the signature to use mode (remove the underscore) and at the start of calculate_open_code_cost check if mode.skips_pricing() and if so return 0.0 (do not honor stored cost_usd), otherwise proceed to use cost_usd and the open_code_model_candidates path; this ensures subscription billing stays zero while missing_open_code_pricing can still rely on mode.skips_pricing().
🤖 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 `@rust/crates/ccusage/src/config.rs`:
- Around line 278-281: The current config application sets shared.mode =
CostMode::Subscription when shared.billing == BillingType::Subscription, but
that can be overridden later by CLI `--mode`; move or reapply this enforcement
after CLI flags are parsed (or, alternatively, when merging CLI flags into
config, ignore/override any CLI-provided mode if shared.billing ==
BillingType::Subscription). Concretely, ensure the invariant is enforced
post-CLI-parse by checking shared.billing == BillingType::Subscription and then
setting shared.mode = CostMode::Subscription (or rejecting a conflicting CLI
`--mode`) so billing remains authoritative.
---
Outside diff comments:
In `@rust/crates/ccusage/src/adapter/opencode/parser.rs`:
- Around line 98-127: calculate_open_code_cost currently ignores the CostMode by
naming the parameter _mode and immediately returning stored cost_usd, which
allows subscription-billed calls to produce non-zero costs; change the signature
to use mode (remove the underscore) and at the start of calculate_open_code_cost
check if mode.skips_pricing() and if so return 0.0 (do not honor stored
cost_usd), otherwise proceed to use cost_usd and the open_code_model_candidates
path; this ensures subscription billing stays zero while
missing_open_code_pricing can still rely on mode.skips_pricing().
🪄 Autofix (Beta)
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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: f415315a-b2e5-457c-ba24-83ef12c889ae
⛔ Files ignored due to path filters (1)
rust/crates/ccusage/src/snapshots/ccusage__config_schema__tests__snapshots_schema_agent_specific_option_edges.snapis excluded by!**/*.snap
📒 Files selected for processing (14)
rust/crates/ccusage-cli/src/lib.rsrust/crates/ccusage-cli/src/types.rsrust/crates/ccusage/src/adapter/claude/daily.rsrust/crates/ccusage/src/adapter/claude/mod.rsrust/crates/ccusage/src/adapter/codebuff/loader.rsrust/crates/ccusage/src/adapter/gemini/parser.rsrust/crates/ccusage/src/adapter/kilo/parser.rsrust/crates/ccusage/src/adapter/kimi/parser.rsrust/crates/ccusage/src/adapter/opencode/loader.rsrust/crates/ccusage/src/adapter/opencode/parser.rsrust/crates/ccusage/src/adapter/qwen/parser.rsrust/crates/ccusage/src/config.rsrust/crates/ccusage/src/config_schema.rsrust/crates/ccusage/src/cost.rs
| // Billing is a stronger signal than mode: subscription always forces zero costs. | ||
| if shared.billing == BillingType::Subscription { | ||
| shared.mode = CostMode::Subscription; | ||
| } |
There was a problem hiding this comment.
Subscription enforcement can be overridden by later CLI --mode.
Line 280 forces CostMode::Subscription during config application, but parser flow applies config before CLI flag parsing. A later --mode calculate can overwrite this and re-enable cost computation even with billing: "subscription". Enforce this invariant after CLI parsing (or ignore --mode when shared.billing == Subscription) so billing remains authoritative.
🤖 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 `@rust/crates/ccusage/src/config.rs` around lines 278 - 281, The current config
application sets shared.mode = CostMode::Subscription when shared.billing ==
BillingType::Subscription, but that can be overridden later by CLI `--mode`;
move or reapply this enforcement after CLI flags are parsed (or, alternatively,
when merging CLI flags into config, ignore/override any CLI-provided mode if
shared.billing == BillingType::Subscription). Concretely, ensure the invariant
is enforced post-CLI-parse by checking shared.billing ==
BillingType::Subscription and then setting shared.mode = CostMode::Subscription
(or rejecting a conflicting CLI `--mode`) so billing remains authoritative.
There was a problem hiding this comment.
4 issues found across 15 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="rust/crates/ccusage/src/adapter/opencode/parser.rs">
<violation number="1" location="rust/crates/ccusage/src/adapter/opencode/parser.rs:127">
P1: Subscription mode is recognized for pricing-skips but OpenCode cost calculation still returns stored positive `cost_usd`, so subscription runs can report non-zero costs.</violation>
</file>
<file name="rust/crates/ccusage/src/config.rs">
<violation number="1" location="rust/crates/ccusage/src/config.rs:279">
P2: `billing: "subscription"` does not reliably force zero-cost mode because `--mode` parsing can overwrite it after config is applied.</violation>
</file>
<file name="rust/crates/ccusage/src/adapter/opencode/loader.rs">
<violation number="1" location="rust/crates/ccusage/src/adapter/opencode/loader.rs:45">
P2: Subscription mode is not fully enforced for OpenCode: embedded positive `cost` values still pass through as non-zero.</violation>
</file>
<file name="rust/crates/ccusage/src/adapter/codebuff/loader.rs">
<violation number="1" location="rust/crates/ccusage/src/adapter/codebuff/loader.rs:47">
P2: Codebuff `Display` mode is not honored: token pricing is still calculated unless mode is exactly `Subscription`.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| pricing: Option<&PricingMap>, | ||
| ) -> Option<String> { | ||
| if mode == CostMode::Display || cost_usd.is_some_and(|cost| cost > 0.0) { | ||
| if mode.skips_pricing() || cost_usd.is_some_and(|cost| cost > 0.0) { |
There was a problem hiding this comment.
P1: Subscription mode is recognized for pricing-skips but OpenCode cost calculation still returns stored positive cost_usd, so subscription runs can report non-zero costs.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At rust/crates/ccusage/src/adapter/opencode/parser.rs, line 127:
<comment>Subscription mode is recognized for pricing-skips but OpenCode cost calculation still returns stored positive `cost_usd`, so subscription runs can report non-zero costs.</comment>
<file context>
@@ -124,7 +124,7 @@ fn missing_open_code_pricing(
pricing: Option<&PricingMap>,
) -> Option<String> {
- if mode == CostMode::Display || cost_usd.is_some_and(|cost| cost > 0.0) {
+ if mode.skips_pricing() || cost_usd.is_some_and(|cost| cost > 0.0) {
return None;
}
</file context>
| apply_shared_options(shared, SharedOptions::from_map(options)); | ||
| } | ||
| // Billing is a stronger signal than mode: subscription always forces zero costs. | ||
| if shared.billing == BillingType::Subscription { |
There was a problem hiding this comment.
P2: billing: "subscription" does not reliably force zero-cost mode because --mode parsing can overwrite it after config is applied.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At rust/crates/ccusage/src/config.rs, line 279:
<comment>`billing: "subscription"` does not reliably force zero-cost mode because `--mode` parsing can overwrite it after config is applied.</comment>
<file context>
@@ -261,9 +268,17 @@ fn is_report_command(command: &str) -> bool {
apply_shared_options(shared, SharedOptions::from_map(options));
}
+ // Billing is a stronger signal than mode: subscription always forces zero costs.
+ if shared.billing == BillingType::Subscription {
+ shared.mode = CostMode::Subscription;
+ }
</file context>
| shared: &SharedArgs, | ||
| ) -> Result<Vec<LoadedEntry>> { | ||
| let pricing = if shared.mode == CostMode::Display { | ||
| let pricing = if shared.mode.skips_pricing() { |
There was a problem hiding this comment.
P2: Subscription mode is not fully enforced for OpenCode: embedded positive cost values still pass through as non-zero.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At rust/crates/ccusage/src/adapter/opencode/loader.rs, line 45:
<comment>Subscription mode is not fully enforced for OpenCode: embedded positive `cost` values still pass through as non-zero.</comment>
<file context>
@@ -42,7 +42,7 @@ pub(crate) fn load_entries_from_directory(
shared: &SharedArgs,
) -> Result<Vec<LoadedEntry>> {
- let pricing = if shared.mode == CostMode::Display {
+ let pricing = if shared.mode.skips_pricing() {
None
} else {
</file context>
| let cost = if mode == CostMode::Subscription { | ||
| 0.0 | ||
| } else { | ||
| calculate_codebuff_cost(&entry, pricing) | ||
| }; |
There was a problem hiding this comment.
P2: Codebuff Display mode is not honored: token pricing is still calculated unless mode is exactly Subscription.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At rust/crates/ccusage/src/adapter/codebuff/loader.rs, line 47:
<comment>Codebuff `Display` mode is not honored: token pricing is still calculated unless mode is exactly `Subscription`.</comment>
<file context>
@@ -40,10 +41,19 @@ fn load_entries_inner(shared: &SharedArgs, pricing: &PricingMap) -> Result<Vec<L
) -> LoadedEntry {
- let cost = calculate_codebuff_cost(&entry, pricing);
- let missing_pricing_model = missing_codebuff_pricing(&entry, pricing);
+ let cost = if mode == CostMode::Subscription {
+ 0.0
+ } else {
</file context>
| let cost = if mode == CostMode::Subscription { | |
| 0.0 | |
| } else { | |
| calculate_codebuff_cost(&entry, pricing) | |
| }; | |
| let cost = if mode.skips_pricing() { | |
| 0.0 | |
| } else { | |
| calculate_codebuff_cost(&entry, pricing) | |
| }; |
The Blacksmith migration (#1249) changed the PR Gate trigger from `pull_request_target` to `pull_request`. For PRs opened by outside contributors, `pull_request` workflows are gated as `action_required` and do not run without a maintainer manually approving them, so the auto-close gate silently stopped firing (e.g. #1281 stayed open). Restore `pull_request_target` so the gate runs with the base repo's write permissions and can close unapproved PRs automatically. This workflow never checks out PR code — it only calls the GitHub API via github-script — so the usual pull_request_target injection risk does not apply. Added an inline comment documenting the requirement to prevent the same regression in future workflow edits.
The PR Gate only fires on `pull_request_target` `opened`, so PRs that were opened while the gate was misconfigured (e.g. #1281) cannot be retroactively auto-closed. Add a `workflow_dispatch` workflow that closes a given PR number with the same contribution-gate message, attributed to github-actions[bot] like the gate itself.
|
This PR was auto-closed. Only contributors approved with 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 See CONTRIBUTING.md. |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — The PR adds a billing top-level config option ("api" / "subscription") to ccusage.json. When set to "subscription", all cost output is forced to $0.00 while token counts remain intact, supporting Claude Team / Max / Pro flat-fee subscription users whose token-calculated costs are misleading.
CostMode::SubscriptionandBillingTypeenums — NewSubscriptionvariant onCostModeforces zero costs in all calculation paths; newBillingTypeenum (Api/Subscription) with config schema and runtime types.CostMode::skips_pricing()helper — Refactorsmode == CostMode::Displaychecks to a method that also coversSubscription, avoiding redundant pricing data loads in adapters.- Adapter updates — All adapters updated to handle
Subscriptionviaskips_pricing()or explicit match arms (Claude, Codebuff, Gemini, Kilo, Kimi, OpenCode, Qwen — 7 adapters). - Config integration —
billingis read from the root ofccusage.json;"subscription"overridesshared.modetoCostMode::Subscriptionregardless of per-command or default mode settings. The override is applied after shared options, so mode always wins. - Schema and tests — JSON schema updated for IDE autocomplete; snapshot regenerated; 4 new unit tests covering subscription override, explicit-mode override, api-leaves-mode, and cost zeroing.
Big Pickle (free via Pullfrog for OSS) | 𝕏
|
Historical audit: this pull request was auto-closed by the legacy contributor gate. That closure did not assess technical importance. Audit result: needs review. The current state does not prove resolution, but a fresh technical or product check is required before deciding whether the underlying request is still relevant. This closed PR will not be revived as-is; create a new PR only after reviewing the related issue and current |

Summary
Subscription users on Claude Team / Max / Pro pay a flat monthly fee, so the token-calculated dollar amounts ccusage shows are misleading — no API bill is being generated. This adds
billing: "subscription"toccusage.jsonas an explicit opt-in that forces all cost output to$0.00while keeping token counts intact.Closes #1280. Relates to #658 (block display for subscription users), #1112 (Hermes subscription $0 billing), and #4 (costUSD removed from JSONL).
Changes
billingtop-level config key ("api"|"subscription") inccusage.jsonbilling: "subscription"activates an internalCostMode::Subscriptionthat short-circuits all cost computation to0.0without loading pricing dataconfig_schema.rs) so the key appears in IDE autocompleteCostMode::skips_pricing()helper avoids redundant pricing loads in adapters that support subscriptionBackground: why existing approaches don't work
The auth/billing type is not recorded in local JSONL files:
service_tieris always"standard"for both API-key and subscription accounts (confirmed across 44k+ JSONL entries)--mode displayreturnscost_usd ?? 0.0which is$0for everyone now that Claude Code droppedcostUSDfrom JSONL in v1.0.9 (issue Claude Code 1.0.9 no longer reports the costUSD field #4)An explicit user-declared opt-in is the only reliable approach.
Known limitations
Three adapters that compute costs without threading
modethrough their internal functions (goose, hermes, droid) will not have costs zeroed under subscription billing. This affects multi-agent users who also use those specific agents alongside Claude Code. The Claude adapter — the primary use case for this feature — handles it correctly.Validation
"billing": "subscription"toccusage.json, runccusage daily— cost column shows$0.00ccusage dailywithout the billing key — existing token-calculated costs unchangedccusage claude dailywith and withoutbilling— same behaviourNeed help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Summary by cubic
Add
billing: "subscription"toccusage.jsonto show $0.00 for subscription plans while keeping token counts. This avoids misleading token-based costs and skips pricing loads.New Features
billingconfig ("api"|"subscription");"subscription"maps toCostMode::Subscriptionand forces zero cost.CostMode::skips_pricing()and updated adapters to bypass pricing loads (Claude, Codebuff, Gemini, Kilo, Kimi, OpenCode, Qwen).billingautocompletes; added tests; CLI exposesBillingType.Migration
"billing": "subscription"inccusage.json. Leave unset or set"api"to keep current behavior.goose,hermes, anddroidare not yet subscription-aware; their costs may not zero.Written for commit 8a32054. Summary will update on new commits.
Summary by CodeRabbit
New Features
billingconfiguration option supporting both API and subscription modes.Refactor