feat(FN-955): add 'request was aborted' to transient error patterns

- Add /request was aborted/i pattern to TRANSIENT_ERROR_PATTERNS for AI provider abort errors
- Cover case-insensitive matching (e.g., Anthropic streaming aborts)
- Add tests for positive matches, negative cases (bare 'abort'), and classifyError behavior
- Ensure abort errors classify as 'transient', not 'usage-limit'
This commit is contained in:
gsxdsm
2026-04-04 21:36:07 -07:00
parent 148b7c1c1e
commit ba90bad3dc
2 changed files with 23 additions and 0 deletions

View File

@@ -78,6 +78,13 @@ describe("Transient Error Detector", () => {
expect(isTransientError("timeout connection to server")).toBe(true);
});
it("matches 'request was aborted' (AI provider abort errors)", () => {
expect(isTransientError("request was aborted")).toBe(true);
expect(isTransientError("Request was aborted")).toBe(true);
expect(isTransientError("REQUEST WAS ABORTED")).toBe(true);
expect(isTransientError("Error: request was aborted")).toBe(true);
});
// Edge cases
it("returns false for empty string", () => {
expect(isTransientError("")).toBe(false);
@@ -122,6 +129,10 @@ describe("Transient Error Detector", () => {
// "timeout" alone is not in the patterns (only connection timeouts)
expect(isTransientError("timeout")).toBe(false);
expect(isTransientError("Request timeout")).toBe(false);
// "abort" alone should not match — only "request was aborted" is transient
expect(isTransientError("abort")).toBe(false);
expect(isTransientError("Aborted")).toBe(false);
expect(isTransientError("The operation was aborted by user")).toBe(false);
});
});
@@ -139,6 +150,13 @@ describe("Transient Error Detector", () => {
expect(classifyError("ECONNREFUSED")).toBe("transient");
expect(classifyError("socket hang up")).toBe("transient");
expect(classifyError("Connection refused")).toBe("transient");
expect(classifyError("request was aborted")).toBe("transient");
});
it("classifies 'Request was aborted' as 'transient', not 'usage-limit'", () => {
// Ensure abort errors are classified as transient, not usage-limit
expect(classifyError("Request was aborted")).toBe("transient");
expect(classifyError("REQUEST WAS ABORTED")).toBe("transient");
});
it("classifies all other errors as 'permanent'", () => {

View File

@@ -27,6 +27,7 @@ import { isUsageLimitError } from "./usage-limit-detector.js";
* - Timeouts (ETIMEDOUT, timeout in connection context)
* - Socket errors (socket hang up)
* - Transport layer failures
* - AI provider abort errors (request was aborted — temporary streaming/API cancellations)
*/
export const TRANSIENT_ERROR_PATTERNS: RegExp[] = [
// Proxy/gateway errors - indicate temporary routing issues
@@ -47,6 +48,10 @@ export const TRANSIENT_ERROR_PATTERNS: RegExp[] = [
// Timeout patterns (only when related to connections, not general timeouts)
/timeout.*connection/i,
/connection.*timeout/i,
// AI provider abort errors — temporary request cancellations (e.g., Anthropic streaming aborts)
// These occur when the provider's infrastructure drops an in-flight request.
/request was aborted/i,
];
/**