langgenius/dify · error · BaseError

server_5xx

server_5xx

Error message

device-flow poll unavailable after retries

What it means

Raised by DatasourceOAuthCallback.get when a context_id is present but OAuthProxyService.use_proxy_context(context_id) returns None — meaning the stored proxy context does not exist or has expired. use_proxy_context both looks up and consumes/invalidates the context, so this fires on second use, expiry, or unknown IDs. Maps to HTTP 403 via werkzeug Forbidden.

Source

Thrown at cli/src/commands/auth/login/device-flow.ts:59

    if (opts.clock.isCancelled()) throw expired()
    const result = await pollWithRetry(api, req, opts.clock)
    switch (result.status) {
      case 'approved':
        return result.success
      case 'pending':
        break
      case 'slow_down':
        interval = Math.min(interval * 2, MAX_INTERVAL_MS)
        break
      case 'expired':
        throw expired()
      case 'denied':
        throw new BaseError({
          code: ErrorCode.AccessDenied,
          message: 'authorization denied',
        })
      case 'retry_5xx':
        throw new BaseError({
          code: ErrorCode.Server5xx,
          message: 'device-flow poll unavailable after retries',
        })
    }
    await opts.clock.sleepMs(interval)
    if (opts.clock.isCancelled()) throw expired()
  }
}

async function pollWithRetry(
  api: DeviceFlowApiSubset,
  req: PollRequest,
  clock: Clock,
): Promise<PollResult> {
  let backoff = POLL_RETRY_INITIAL_MS
  for (let attempt = 1; attempt <= POLL_RETRY_ATTEMPTS; attempt++) {
    const result = await api.pollOnce(req)
    if (result.status !== 'retry_5xx') return result

View on GitHub (pinned to ef8544b173)

Solutions

  1. Restart the OAuth flow from get-authorization-url to mint a fresh context_id.
  2. Ensure the context store (e.g. Redis) is shared across all API instances and not being flushed.
  3. Increase OAuthProxyService.__MAX_AGE__ if users routinely exceed the TTL during provider authorization.
  4. Make the callback handler idempotent on the client side so a duplicate redirect does not re-trigger the consumed context.

Example fix

// before — client retries the callback on transient failure
fetch(callbackUrl);  // second hit -> Invalid context_id
// after — on 403 Invalid context_id, restart the flow
const res = await fetch(callbackUrl);
if (res.status === 403) { window.location = `/console/api/oauth/plugin/${providerId}/datasource/get-authorization-url`; }
Defensive patterns

Strategy: retry

Validate before calling

// cannot validate server-side context from the client; instead detect 403 and restart
// pre-check: ensure the context was created recently
const startedAt = Number(sessionStorage.getItem('oauth_started_at') || 0);
const stale = Date.now() - startedAt > OAuthProxy_MAX_AGE_MS;
if (stale) { restartFlow(); }

Type guard

interface ProxyContext { user_id: string; tenant_id: string; }
function isValidProxyContext(c: unknown): c is ProxyContext {
  return typeof c === 'object' && c !== null
    && typeof (c as ProxyContext).user_id === 'string'
    && typeof (c as ProxyContext).tenant_id === 'string';
}

Try / catch

try {
  await fetch(callbackUrl);
} catch (e) {
  if (e.response?.status === 403 && /Invalid context_id/i.test(e.response.data?.message || '')) {
    // context consumed or expired — restart exactly once
    window.location = `/console/api/oauth/plugin/${providerId}/datasource/get-authorization-url`;
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: User reloads or reopens the callback URL after the context was already consumed; the context TTL elapsed between authorization and callback; the context was never persisted (e.g. Redis flushed); context_id was tampered with.

Common situations: Double callback (provider retries, or user refreshes the callback page); short context TTL combined with a long OAuth provider user-interaction; shared cache (Redis) eviction; load balancer routing the callback to an instance without access to the context store.

Related errors


AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12). Data as JSON: /api/errors/7ec94fa42486fea3. Report an issue: GitHub.