Describe the Bug
isRetryableModelError() (packages/runtime/src/submission-state.ts:439-451 on current main, bf86b87) is the classifier that routes transient model failures into the in-loop transient retry (classifyResumeState → mode: 'transient_retry'). Its regex covers network.?error, connection.?(?:reset|refused|lost), socket hang up, fetch failed, timeouts, 5xx, etc. — but it does not match the literal string "Connection error."
That exact string is the default message of APIConnectionError in both official provider SDKs:
- openai (
core/error.ts): super(undefined, undefined, message || 'Connection error.', undefined) — verified in openai 6.26.0, core/error.js:80
- @anthropic-ai/sdk (
core/error.ts): same literal — verified in 0.91.1, core/error.js:78
APIConnectionError is what these SDKs throw for every connection-layer failure — DNS resolution failure, TLS handshake failure, proxy disconnect, socket aborted before a response — so "Connection error." is arguably the single most common transient error text a production deployment will ever see from these providers. Because the classifier doesn't match it, the assistant error entry is treated as permanent: no transient retry happens and the submission settles as failed.
In production we observed a single transient proxy blip permanently fail a submission with attempt_count = 2 while maxAttempts was configured to 10; the durable workflow driving that submission was then failed as a whole. (Durability-level attempts don't help here for the same reason as in #443 — the error is observed and classified, just classified as permanent.)
This is the same class of gap as #443 ("Stream ended without finish_reason" not classified retryable), fixed in 2.0: the provider layer produces a transient connection-layer error, but the retry-classification layer treats it as terminal.
Affected: @flue/runtime 2.0.3 (latest) and current main.
Expected Behavior
stopReason: 'error' with errorMessage: 'Connection error.' (optionally with SDK cause suffixes appended) should be classified retryable, exactly like connection reset / connection refused / network error already are, so the submission retries up to its attempt budget instead of settling failed on the first network blip.
Steps to Reproduce
Deterministic, classifier-level (same shape as the repro in #443):
import { isRetryableModelError } from './packages/runtime/src/submission-state';
isRetryableModelError({
role: 'assistant',
stopReason: 'error',
errorMessage: 'Connection error.', // openai / @anthropic-ai/sdk APIConnectionError default message
content: [],
});
// expected: true
// actual: false
End-to-end: run any OpenAI-backed agent behind a proxy and drop the connection mid-request once — the OpenAI SDK throws APIConnectionError('Connection error.'), and the submission hard-fails instead of retrying.
Proposed fix
Minimal, mirroring the existing alternation style — add connection.?error| next to the existing connection branch in the isRetryableModelError regex:
- return /overloaded|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|network.?error|connection.?(?:reset|refused|lost)|socket hang up|fetch failed|timed? out|timeout|terminated|provider finish_reason:\s*error(?![-\w])/i.test(
+ return /overloaded|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|network.?error|connection.?error|connection.?(?:reset|refused|lost)|socket hang up|fetch failed|timed? out|timeout|terminated|provider finish_reason:\s*error(?![-\w])/i.test(
message.errorMessage,
);
Checked against the current pattern: with this change "Connection error." classifies retryable, while clearly non-retryable messages (e.g. "invalid api key") still classify non-retryable, and all strings matched by the current regex still match.
We're running this exact one-branch patch in production (via pnpm patch) and it resolves the failure mode. Happy to provide anything else that helps — filing this as a fix proposal per CONTRIBUTING.md rather than a PR.
Describe the Bug
isRetryableModelError()(packages/runtime/src/submission-state.ts:439-451on currentmain,bf86b87) is the classifier that routes transient model failures into the in-loop transient retry (classifyResumeState→mode: 'transient_retry'). Its regex coversnetwork.?error,connection.?(?:reset|refused|lost),socket hang up,fetch failed, timeouts, 5xx, etc. — but it does not match the literal string "Connection error."That exact string is the default message of
APIConnectionErrorin both official provider SDKs:core/error.ts):super(undefined, undefined, message || 'Connection error.', undefined)— verified in openai6.26.0,core/error.js:80core/error.ts): same literal — verified in0.91.1,core/error.js:78APIConnectionErroris what these SDKs throw for every connection-layer failure — DNS resolution failure, TLS handshake failure, proxy disconnect, socket aborted before a response — so "Connection error." is arguably the single most common transient error text a production deployment will ever see from these providers. Because the classifier doesn't match it, the assistant error entry is treated as permanent: no transient retry happens and the submission settles asfailed.In production we observed a single transient proxy blip permanently fail a submission with
attempt_count = 2whilemaxAttemptswas configured to 10; the durable workflow driving that submission was then failed as a whole. (Durability-level attempts don't help here for the same reason as in #443 — the error is observed and classified, just classified as permanent.)This is the same class of gap as #443 ("Stream ended without finish_reason" not classified retryable), fixed in 2.0: the provider layer produces a transient connection-layer error, but the retry-classification layer treats it as terminal.
Affected:
@flue/runtime2.0.3 (latest) and currentmain.Expected Behavior
stopReason: 'error'witherrorMessage: 'Connection error.'(optionally with SDK cause suffixes appended) should be classified retryable, exactly likeconnection reset/connection refused/network erroralready are, so the submission retries up to its attempt budget instead of settlingfailedon the first network blip.Steps to Reproduce
Deterministic, classifier-level (same shape as the repro in #443):
End-to-end: run any OpenAI-backed agent behind a proxy and drop the connection mid-request once — the OpenAI SDK throws
APIConnectionError('Connection error.'), and the submission hard-fails instead of retrying.Proposed fix
Minimal, mirroring the existing alternation style — add
connection.?error|next to the existing connection branch in theisRetryableModelErrorregex:Checked against the current pattern: with this change
"Connection error."classifies retryable, while clearly non-retryable messages (e.g."invalid api key") still classify non-retryable, and all strings matched by the current regex still match.We're running this exact one-branch patch in production (via
pnpm patch) and it resolves the failure mode. Happy to provide anything else that helps — filing this as a fix proposal per CONTRIBUTING.md rather than a PR.