Skip to content

fix(billing): charge 0% on subscription-served turns (Anthropic + Codex)#513

Merged
steventohme merged 1 commit into
mainfrom
steven/zero-subscription-fee
Jun 25, 2026
Merged

fix(billing): charge 0% on subscription-served turns (Anthropic + Codex)#513
steventohme merged 1 commit into
mainfrom
steven/zero-subscription-fee

Conversation

@steventohme

Copy link
Copy Markdown
Collaborator

What

Stop charging the 5% routing margin on turns served by the customer's own Claude/Codex subscription OAuth token. Those turns are paid for by the customer's own plan — Weave shouldn't skim a margin on tokens it never paid for.

DebitForInference now debits 0 for a subscription-served turn (same as an override pass-through). The full notional cost is still written to the ledger (NotionalCostMicros) as a shadow trail, so we keep visibility without billing. Removes the now-unused SubscriptionFeeRate const and subscriptionFeeMicros helper.

Non-subscription (Weave-fronted / BYOK-routed) turns are unaffected — still debited at cost.

Why

servedOnSubscription is true when the resolved credential is a Claude or Codex OAuth subscription token (creds.OAuth), so this covers both. Charging a % of notional on a cost the customer's own plan already covers surprises customers and is out of step with how gateways price routing on customer-owned credentials (the norm is 0% on BYOK/subscription + a platform/seat fee).

Note

Revenue corollary for product: with subscription turns at 0%, subscription-heavy customers generate ~$0 of inference revenue (only non-subscription routing draws down credits, at cost). Monetizing those customers will need a per-seat/platform fee — separate decision, not in this PR.

Test

go build ./... clean; go test ./internal/billing/... passes. TestDebitForInference_SubscriptionDebitsOnlyTheFee renamed → ...DebitsNothing and updated to assert a 0 debit.

🤖 Generated with Claude Code

A turn served on the customer's own Claude or Codex subscription OAuth token
already has its tokens paid for by that plan, so Weave should not skim a 5%
routing margin on top. Debit 0 for these turns (same as the override case);
the full notional cost is still recorded on the ledger as a shadow trail.

Removes the now-unused SubscriptionFeeRate const and subscriptionFeeMicros
helper. Non-subscription (Weave-fronted) turns are unaffected — still debited
at cost.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jun 25, 2026

Copy link
Copy Markdown

T-Rex T-Rex Logs

What T-Rex did

  • Validated the 5% subscription fee flow by comparing the before and after states, showing the base balance changed from 9,662,500 with delta_usd_micros -337,500 to 10,000,000 with delta_usd_micros 0, while notional_cost_micros remained 6,750,000.
  • Compared the before log for nonsubscription debit cost to verify the base commit debited the full computed cost and recorded matching notional.
  • Compared the after log for nonsubscription debit cost to verify the head commit debited the same full computed cost and recorded matching notional.

View all artifacts

T-Rex Ran code and verified through T-Rex

Reviews (1): Last reviewed commit: "fix(billing): charge 0% on subscription-..." | Re-trigger Greptile

@steventohme
steventohme merged commit 33c8f5f into main Jun 25, 2026
9 checks passed
@steventohme
steventohme deleted the steven/zero-subscription-fee branch June 25, 2026 15:47
rohith500 added a commit to rohith500/router that referenced this pull request Jun 25, 2026
…ppear in dashboard metrics

bypassToAnthropic returned at service.go:1564 before fireTelemetry
at line 2178 was ever reached, so turns served via the subscription
usage-bypass gate wrote no row to router_request_telemetry. They
were invisible to /metrics/details, the model distribution widget,
cost savings charts, and the substitution rate stat card.

Fix: add fireTelemetry call in bypassToAnthropic after otel.Flush,
gated on installationID != uuid.Nil (matching the ProxyMessages gate).
Fields follow the same shape as the normal path:
  - DecisionReason: 'usage_bypass'
  - SubscriptionServed: true (billing stays 0 per workweave#513)
  - TurnType: 'main_loop' (bypass only engages on scorer branch)
  - Costs zeroed (subscription-served turns debit nothing)
  - UpstreamStatusCode captured before proxyErr is cleared so real
    4xx/5xx statuses (e.g. 429) are recorded correctly
  - UpstreamLatencyMs captured immediately after p.Proxy returns,
    before otel.Record/Flush, so OTel export time is not included

The bypassToAnthropic comment listed what it deliberately skips
(scorer, planner, session pin, cache, billing) — telemetry was
absent from that list, confirming the omission was unintentional.

Also adds TestBypassToAnthropic_RecordsTelemetry_UpstreamError to
cover the 4xx path: sets proxyErr to *providers.UpstreamErrorResponse
with Status 429 and asserts UpstreamStatusCode == 429 in the telemetry
row, guarding against regressions that move the status capture after
the proxyErr clear.

Manually verified before and after fix:
  Before: usage_bypass request produces no DB row
  After:  row written with decision_reason=usage_bypass,
          decision_model=claude-haiku-4-5, provider=anthropic

Fixes workweave#516.

Signed-off-by: N Rohith Reddy <rohithreddy2202@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant