Skip to content

fix(failover): recognize 'abort' stop reason as timeout for model fallback#18618

Merged
steipete merged 1 commit intoopenclaw:mainfrom
sauerdaniel:fix/failover-abort-pattern
Feb 16, 2026
Merged

fix(failover): recognize 'abort' stop reason as timeout for model fallback#18618
steipete merged 1 commit intoopenclaw:mainfrom
sauerdaniel:fix/failover-abort-pattern

Conversation

@sauerdaniel
Copy link
Copy Markdown
Contributor

@sauerdaniel sauerdaniel commented Feb 16, 2026

Problem

When streaming providers (GLM, OpenRouter, NVIDIA NIM, etc.) return stop reason: abort due to stream interruption, OpenClaw's failover mechanism did not recognize this as a timeout condition. This prevented fallback models from being triggered, leaving users with failed requests instead of graceful failover to secondary models.

Root Cause

The timeout error pattern detection in two places did not include abort-related patterns:

  • ERROR_PATTERNS.timeout in src/agents/pi-embedded-helpers/errors.ts
  • TIMEOUT_HINT_RE in src/agents/failover-error.ts

Solution

Add abort stop reason patterns to both timeout detection regexes:

  • /\bstop reason:\s*abort\b/i
  • /\breason:\s*abort\b/i
  • /\bunhandled stop reason:\s*abort\b/i

Impact

Affects all users configuring model fallback chains with streaming providers. Before this fix, any abort from the provider would surface as a user-facing error instead of triggering the next model in the fallback chain.

Testing

  • TypeScript compilation passes
  • Lint passes
  • Pattern logic is unit-testable via existing error classification helpers

Fixes #18453

AI-Assisted

This PR was prepared with assistance from Claude Opus 4.5.

Greptile Summary

This PR adds abort stop reason pattern detection to both timeout error classification paths (failover-error.ts and errors.ts), enabling model fallback when streaming providers (GLM, OpenRouter, NVIDIA NIM) return stop reason: abort due to stream interruption.

  • Adds stop reason:\s*abort and unhandled stop reason:\s*abort patterns to TIMEOUT_HINT_RE in failover-error.ts
  • Adds three regex patterns (stop reason:\s*abort, reason:\s*abort, unhandled stop reason:\s*abort) to ERROR_PATTERNS.timeout in errors.ts
  • Inconsistency: The broader reason:\s*abort pattern exists only in errors.ts but not in failover-error.ts, which means the two timeout detection codepaths (isTimeoutError vs isTimeoutErrorMessage) will behave differently for error messages containing "reason: abort" without the "stop" prefix

Confidence Score: 3/5

  • Low-risk regex additions with a minor pattern inconsistency between the two detection paths that could cause edge-case behavioral differences.
  • The core change is correct and addresses a real gap in abort detection for streaming providers. However, the reason:\s*abort pattern is present in errors.ts but missing from failover-error.ts, creating an inconsistency between the two timeout classification paths. This means certain error messages may be classified as timeouts in one path but not the other, potentially causing subtle failover behavior differences. The redundant unhandled stop reason pattern is a minor style concern.
  • src/agents/failover-error.ts — missing reason:\s*abort pattern that exists in errors.ts

Last reviewed commit: d77099a

Local Validation

pnpm build && pnpm check

TypeScript compilation passes. Lint passes. Pattern logic is unit-testable via existing error classification helpers.

…lback

When streaming providers (GLM, OpenRouter, etc.) return 'stop reason: abort'
due to stream interruption, OpenClaw's failover mechanism did not recognize
this as a timeout condition. This prevented fallback models from being
triggered, leaving users with failed requests instead of graceful failover.

Changes:
- Add abort patterns to ERROR_PATTERNS.timeout in pi-embedded-helpers/errors.ts
- Extend TIMEOUT_HINT_RE regex to include abort patterns in failover-error.ts

Fixes openclaw#18453

Co-authored-by: James <james@openclaw.ai>
Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +3 to +4
const TIMEOUT_HINT_RE =
/timeout|timed out|deadline exceeded|context deadline exceeded|stop reason:\s*abort|unhandled stop reason:\s*abort/i;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pattern mismatch with errors.ts

ERROR_PATTERNS.timeout in errors.ts includes the broad /\breason:\s*abort\b/i pattern, but TIMEOUT_HINT_RE here does not. This means the two timeout detection paths behave differently: isTimeoutErrorMessage() (errors.ts) will classify a message containing "reason: abort" as a timeout, but hasTimeoutHint() / isTimeoutError() (this file) will not.

This could cause inconsistent behavior — e.g., a provider error surfaced as an error message string would trigger failover via classifyFailoverReasonisTimeoutErrorMessage, but the same text passed through resolveFailoverReasonFromErrorisTimeoutError would miss it.

If reason:\s*abort (without "stop" prefix) is a real pattern from providers, consider adding it here too for consistency:

Suggested change
const TIMEOUT_HINT_RE =
/timeout|timed out|deadline exceeded|context deadline exceeded|stop reason:\s*abort|unhandled stop reason:\s*abort/i;
const TIMEOUT_HINT_RE =
/timeout|timed out|deadline exceeded|context deadline exceeded|stop reason:\s*abort|reason:\s*abort|unhandled stop reason:\s*abort/i;
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/failover-error.ts
Line: 3:4

Comment:
**Pattern mismatch with `errors.ts`**

`ERROR_PATTERNS.timeout` in `errors.ts` includes the broad `/\breason:\s*abort\b/i` pattern, but `TIMEOUT_HINT_RE` here does not. This means the two timeout detection paths behave differently: `isTimeoutErrorMessage()` (errors.ts) will classify a message containing "reason: abort" as a timeout, but `hasTimeoutHint()` / `isTimeoutError()` (this file) will not.

This could cause inconsistent behavior — e.g., a provider error surfaced as an error message string would trigger failover via `classifyFailoverReason``isTimeoutErrorMessage`, but the same text passed through `resolveFailoverReasonFromError``isTimeoutError` would miss it.

If `reason:\s*abort` (without "stop" prefix) is a real pattern from providers, consider adding it here too for consistency:

```suggestion
const TIMEOUT_HINT_RE =
  /timeout|timed out|deadline exceeded|context deadline exceeded|stop reason:\s*abort|reason:\s*abort|unhandled stop reason:\s*abort/i;
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +602 to +604
/\bstop reason:\s*abort\b/i,
/\breason:\s*abort\b/i,
/\bunhandled stop reason:\s*abort\b/i,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant pattern: unhandled stop reason already matched

/\bstop reason:\s*abort\b/i will already match the substring "stop reason: abort" inside "unhandled stop reason: abort" (since \b fires at the word boundary before "stop"). This makes /\bunhandled stop reason:\s*abort\b/i on line 604 redundant — it can never match something that line 602 wouldn't already catch.

Not a bug, but worth simplifying to reduce maintenance surface:

Suggested change
/\bstop reason:\s*abort\b/i,
/\breason:\s*abort\b/i,
/\bunhandled stop reason:\s*abort\b/i,
/\bstop reason:\s*abort\b/i,
/\breason:\s*abort\b/i,

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-helpers/errors.ts
Line: 602:604

Comment:
**Redundant pattern: `unhandled stop reason` already matched**

`/\bstop reason:\s*abort\b/i` will already match the substring "stop reason: abort" inside "unhandled stop reason: abort" (since `\b` fires at the word boundary before "stop"). This makes `/\bunhandled stop reason:\s*abort\b/i` on line 604 redundant — it can never match something that line 602 wouldn't already catch.

Not a bug, but worth simplifying to reduce maintenance surface:

```suggestion
    /\bstop reason:\s*abort\b/i,
    /\breason:\s*abort\b/i,
```

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: XS labels Feb 16, 2026
@steipete steipete merged commit 12ce358 into openclaw:main Feb 16, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: fix(failover): Add "abort" to timeout error patterns for fallback triggering

2 participants