Skip to content

OpenAI/Anthropic SDK "Connection error." not classified retryable, so one network blip permanently fails the submission #629

Description

@Coiggahou2002

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 (classifyResumeStatemode: '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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions