Skip to content

feat(sdk): define the auth() callback contract — per-request cost, caching, retry staleness #458

Open

Description

@EricAndrechek

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:

  1. Document the contract. State plainly that auth is called per request and must be cheap — cache and refresh on your side. Lowest cost, and arguably sufficient.
  2. 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

Activity

  1. added
    enhancementNew feature or request
    area/sdkTypeScript SDK (clients/ts/)
    area/authAuthentication: tokens, JWT/JWKS, keys, token expiry/revocation
    on Aug 12, 2026
  2. coderabbitai commented on Aug 12, 2026

    @coderabbitai
    🔗 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:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  3. added a commit that references this issue on Oct 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/authAuthentication: tokens, JWT/JWKS, keys, token expiry/revocationarea/sdkTypeScript SDK (clients/ts/)enhancementNew feature or request

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions