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 resultView on GitHub (pinned to ef8544b173)
Solutions
- Restart the OAuth flow from get-authorization-url to mint a fresh context_id.
- Ensure the context store (e.g. Redis) is shared across all API instances and not being flushed.
- Increase OAuthProxyService.__MAX_AGE__ if users routinely exceed the TTL during provider authorization.
- 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
- Make the callback handling single-use: do not refresh or retry the callback URL.
- Ensure the context store (Redis) is shared across API instances and not flushed mid-flow.
- Tune OAuthProxyService.__MAX_AGE__ to exceed the provider's longest user-interaction time.
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
- access_denied
- export response missing data field
- reconnect stream body missing
- unknown
- usage_missing_arg
AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12).
Data as JSON: /api/errors/7ec94fa42486fea3.
Report an issue: GitHub.