Skip to content

[Bug]: Model fallback not triggered when Antigravity model times out #313

@erikpr1994

Description

@erikpr1994

Summary

Model fallback doesn't trigger when the primary Antigravity model times out. The AbortError from session.abort() escapes via waitForCompactionRetry() and bypasses the fallback mechanism in runWithModelFallback().

Steps to reproduce

  1. Configure fallback models in ~/.clawdbot/clawdbot.json:
    "agent": {
      "model": {
        "primary": "google-antigravity/claude-opus-4-5-thinking",
        "fallbacks": ["google-antigravity/gemini-3-pro-low", "google-antigravity/gemini-3-flash"]
      }
    }
  2. Send a message that triggers an agent run when the primary model is slow/rate-limited
  3. Wait for timeout (10 minutes) - observe no fallback models are tried

Expected behavior

After the primary model times out, clawdbot should automatically try fallback models in order until one succeeds, then return the response from whichever model worked.

Actual behavior

Timeout triggers session.abort() → throws AbortError → escapes from waitForCompactionRetry() (outside try/catch) → model-fallback.ts:199 sees AbortError and immediately re-throws without trying fallbacks → request fails with "Request was aborted".

The fallback models are never attempted.

Environment

  • Clawdbot version: 2026.1.5-3
  • OS: macOS Darwin 25.1.0 (arm64)
  • Install method: pnpm (source)

Logs or screenshots

[agent/embedded] embedded run timeout: runId=d6165b71-abc5-420c-95b7-00c41675d877 sessionId=24e1c3c4-a457-463c-94d6-abfd17868d8c timeoutMs=600000
[agent/embedded] Profile google-antigravity:xxx@gmail.com timed out (possible rate limit). Trying next account...
Embedded agent failed before reply: Request was aborted
[gws] ⇄ res ✗ chat.send 600248ms errorCode=UNAVAILABLE errorMessage=Error: Request was aborted

Root cause analysis

In pi-embedded-runner.ts, waitForCompactionRetry() at line 899 is outside the try/catch that captures promptError:

try {
  await session.prompt(params.prompt);
} catch (err) {
  promptError = err;  // Only catches errors from session.prompt()
}
await waitForCompactionRetry();  // This can ALSO throw AbortError!

When timeout fires:

  1. abortRun(true) is called → sets timedOut = true and calls session.abort()
  2. session.prompt() throws AbortError → caught in promptError
  3. waitForCompactionRetry() ALSO throws AbortError → NOT caught, escapes!
  4. model-fallback.ts:199: if (isAbortError(err)) throw err; → bypasses fallback

Proposed fix

Wrap waitForCompactionRetry() in its own try/catch:

try {
  await waitForCompactionRetry();
} catch (err) {
  if (!promptError) promptError = err;
}

This ensures the AbortError is captured as promptError, allowing the timeout handling logic at line 970+ to throw a regular Error("LLM request timed out.") that triggers the model fallback mechanism.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions