Repository navigation
Description
Area: sdk — performance / DX · split out of #269
ClientConfig.auth is a callback the SDK awaits to obtain a bearer token. It is invoked once per request, with no caching, no in-flight dedupe, and no documented expectation about cost.
// clients/ts/src/http.ts:47-52
if (ctx.auth) {
const token = await ctx.auth();
if (token) headers.Authorization = `Bearer ${token}`;
}That is free when the callback returns a value already in memory — the shape most examples show (auth: () => localStorage.getItem('token')). It is a network round trip on every single request for anyone whose callback mints or refreshes a token, which is a natural pattern for exactly the proxied / BFF / gateway deployments in #269 and #428. Nothing in the docs says which of those the SDK expects, so the expensive shape looks supported and is silently slow.
Two directions, not mutually exclusive:
- Document the contract. State plainly that
authis called per request and must be cheap — cache and refresh on your side. Lowest cost, and arguably sufficient. - Dedupe in the SDK. Collapse concurrent in-flight calls (so a burst of parallel queries mints one token, not N), optionally with a short TTL. More helpful, but it needs a defined invalidation story — a cached token that outlives its expiry is worse than no cache, so it likely needs either a TTL the caller sets or a 401-triggered refresh.
Correction to the original #269 note
The #269 comment that raised this said auth() runs "again on every retry attempt". That part is not accurate — worth recording so it doesn't get designed around. auth() is called at http.ts:48, before the retry loop opens at http.ts:57, so a request that retries 3 times still calls auth() exactly once. Retries reuse the header computed up front.
That has its own (separate, lower-priority) consequence worth deciding on: if a token expires during a retry-and-backoff sequence, the retries keep sending the stale one, and the 503 Retry-After path can wait 30s by default. Whether to re-mint on retry is a real question, but it is the opposite trade from caching, and both should be settled together.
Not in scope
options.fetch (#456) is a workaround for the determined — a wrapper can cache tokens itself — but it is not an answer for the default path, and it does not apply to the SSE transport, which calls auth() separately (stream/sse.ts:65-68).
— Filed by Claude Opus 5, via Claude Code
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsBacklog
Activity
coderabbitai commentedon Aug 12, 2026
🔗 Related PRs
#448 - fix(sdk): keep a baseURL path prefix instead of discarding it [merged]
#456 - feat(sdk): allow overriding the HTTP implementation via options.fetch [open]
🧪 Issue enrichment is currently in open beta.
You can configure auto-planning by selecting labels in the issue_enrichment configuration.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:💬 Have feedback or questions? Drop into our discord!
feat(sdk)!: add options.headers, fetchOptions, and fetch (#456)
fix(typelayer): drop the #458 latch from the setup-refusal cause