Skip to content

OpenAI SDK retries sleep full Retry-After (days) and Escape cannot abort #6911

Description

@catoncat

Summary

When retry.provider.maxRetries > 0, pi-ai passes that value into the OpenAI/Anthropic SDK maxRetries option. Those SDKs:

  1. treat every HTTP 429 as retryable
  2. sleep the full Retry-After with no delay cap
  3. use a plain setTimeout sleep that ignores AbortSignal

So a usage-limit 429 with a multi-day Retry-After freezes the agent on working, and Escape cannot interrupt.

This is separate from the provider actually being out of quota: the provider correctly returns 429, but Pi's handling makes the failure look like a hang.

Reproduction

Provider evidence (OpenCode Go)

curl -sS -D - \
  -H "Authorization: Bearer $OPENCODE_GO_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"model":"kimi-k3","messages":[{"role":"user","content":"hi"}],"max_tokens":8}' \
  https://opencode.ai/zen/go/v1/chat/completions

Observed:

HTTP/2 429
retry-after: 277403
{
  "type": "error",
  "error": {
    "type": "GoUsageLimitError",
    "message": "Monthly usage limit reached. Resets in 3 days. ..."
  }
}

With the OpenAI client (maxRetries: 0) this fails in ~800ms.
With maxRetries: 2 (as configured via Pi settings) the client starts sleeping ~3 days.

Pi evidence

pi --provider opencode-go --model kimi-k3 --no-session --no-tools -p "hi"
  • process opens HTTPS to OpenCode
  • UI / non-interactive run stays in working with no output
  • Escape / abort does not end the wait while the SDK is in sleep(retry-after)

Root settings that re-enable the hole (defaults are safer):

{
  "retry": {
    "provider": {
      "maxRetries": 2,
      "maxRetryDelayMs": 60000
    }
  }
}

maxRetryDelayMs is not applied on the openai-completions path; only some custom transports (e.g. Codex) cap delays.

Why Escape fails

OpenAI SDK:

// openai/internal/utils/sleep.mjs
export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// openai/client.mjs retryRequest
timeoutMillis = timeoutSeconds * 1000; // full Retry-After, no cap
await sleep(timeoutMillis);

Pi Escape calls agent.abort() → AbortSignal, but the SDK retry sleep does not listen to it.

Expected

  1. Usage-limit / multi-day Retry-After should surface as a normal error immediately (or within agent-level abortable backoff).
  2. Escape must be able to cancel provider waits.
  3. retry.provider.maxRetryDelayMs must actually bound provider waits, or SDK retries must stay disabled.

Related

  • fix(ai): disable hidden provider 429 retries #4991 disabled default SDK retries (maxRetries ?? 0) but still forwarded explicit options.maxRetries from coding-agent settings, which reopens this hole when users set retry.provider.maxRetries > 0.
  • Docs already warn to keep provider maxRetries at 0, but a mis-set value currently freezes the session instead of failing closed.

Suggested fix

Always force OpenAI/Anthropic SDK maxRetries: 0. Keep abortable agent-level retry, which already classifies terminal quota/billing/usage-limit errors as non-retryable.

PR incoming.

Metadata

Metadata

Assignees

No one assigned

    Labels

    no-actionThis issue has been rejected after triage

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions