Repository navigation
fix(provider): keep direct-service keys off receipt lookups - #1894
Merged
Merged
Conversation
A direct service's late receipt recovery used to follow an attempt base that classified as OpenRouter, sending that service's own key to the router's generation route. Direct clients now stop before any receipt lookup, whatever the attempt base; ordinary usage billing still runs first, and the router road recovers its receipts exactly as before. The overflow receipt test asserted the old behaviour, so it is replaced by a direct-service test across the configured base, its metered door and an OpenRouter attempt, and a torn router answer that records each request's host and bearer. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
receiptResult gave the worker five seconds of wall clock to report. Under a shared box's load (average twenty on eight cores) that budget was exceeded twice by tests that pass in a tenth of a second alone: TestLateReceiptFollowsTheOverflowRoute and TestK10TrimmedGenerationPairsRecoveryAndBlankUsesAttempt. The ceiling is a hang detector, so it moves to thirty seconds; a passing test still returns the moment the worker reports. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Oct 22, 2026
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was true
Client.settlerecovers a late OpenRouter receipt for a torn, cut or unmetered streamed answer. Its direct-service guard returned early unless the attempt's base classified as OpenRouter, in which casefetchReceiptAtsent the direct service's own key toopenrouter.ai/.../generation. #1887'sTestLateReceiptFollowsTheOverflowRouteasserted that behaviour.What is true now
A direct client returns before any receipt lookup, whatever the attempt base. Ordinary usage billing still runs first, and the router road's late-receipt recovery is unchanged.
Reach
No shipped configuration reached the old path. Every shipped overflow door (Z.ai, Moonshot and Qwen pay-as-you-go) belongs to the same vendor as the configured service, so the attempt base never classified as OpenRouter for a direct client. This closes the guard before any future door could open it.
Tests (from the contract, fake transports recording host and bearer)
TestV1V3DirectLateReceiptNeverUsesAnOverflowHost: a direct client on its configured base, its metered door, and an OpenRouter attempt base, for torn, stalled and unmetered endings with and without usage. It makes no lookup and banks nothing new; a reported-usage answer is billed exactly once, as before.TestV2RouterTornAnswerRecoversWithItsOwnBearer: a torn router answer is still recovered fromopenrouter.aiwith the router's own bearer: two requests, the same recovered price and tokens, and one recovered usage event.GET /api/v1/generationcarryingBearer vendor-key. After it, they pass.Gates:
gofmt,go vet ./internal/provider/,go test -count=1for provider, telemetry and config,make test-laws, andmake pr-ready.Follow-ups found by the audit, not changed here
Both can change behaviour a person sees, so they need their own decision:
internal/provider/media.go~252/284/387): a provider-supplied absolutepolling_urlis fetched with the client's bearer whatever its host. Downloads already checksameHostAsBase; polling does not.Authorizationto subdomains of the original host. Credential-free probes already refuse redirects.Also in this PR
receiptResult's five-second wait for the receipt worker is now a thirty-second hang ceiling. Under shared-box load it was exceeded by two receipt tests that pass in a tenth of a second alone; a passing test still returns the moment the worker reports. Test-only.🤖 Generated with Claude Code