fix: classify stream-truncation JSON parse errors as transient (#2636)
When the API stream is truncated mid-chunk, pi reassembles the partial tool-call JSON and gets a SyntaxError (e.g. "Expected double-quoted property name", "Unexpected end of JSON input"). classifyProviderError() did not match these patterns and fell through to the "unknown = permanent" default, pausing auto-mode indefinitely instead of retrying. These JSON parse errors are the downstream symptom of a connection drop — same root cause as ECONNRESET, one layer up. Add an isMalformedStream guard that matches common JSON SyntaxError patterns and classifies them as transient with the same 15s backoff as connection errors. Closes #2572
This commit is contained in:
parent
8358f262e9
commit
bbd9468c78
2 changed files with 33 additions and 0 deletions
|
|
@ -46,6 +46,15 @@ export function classifyProviderError(errorMsg: string): {
|
|||
return { isTransient: true, isRateLimit: false, suggestedDelayMs: 15_000 }; // 15s for connection errors
|
||||
}
|
||||
|
||||
// Stream-truncation JSON parse errors — transient (#2572).
|
||||
// When the API stream is cut mid-chunk, pi tries to reassemble the partial
|
||||
// tool-call JSON and gets a SyntaxError. This is the downstream symptom of
|
||||
// a connection drop — same root cause as ECONNRESET, one layer up.
|
||||
const isMalformedStream = /Unexpected end of JSON|Unexpected token.*JSON|Expected double-quoted property name|SyntaxError.*JSON/i.test(errorMsg);
|
||||
if (isMalformedStream) {
|
||||
return { isTransient: true, isRateLimit: false, suggestedDelayMs: 15_000 }; // 15s, same as connection errors
|
||||
}
|
||||
|
||||
// Unknown error — treat as permanent (user reviews)
|
||||
return { isTransient: false, isRateLimit: false, suggestedDelayMs: 0 };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,3 +47,27 @@ test("#2309: rate limits are still transient", () => {
|
|||
assert.equal(rlResult.isTransient, true, "rate limits are still transient");
|
||||
assert.equal(rlResult.isRateLimit, true, "rate limits are flagged as rate limits");
|
||||
});
|
||||
|
||||
// --- #2572: stream-truncation JSON parse errors should be transient ---
|
||||
|
||||
test("#2572: 'Expected double-quoted property name' (truncated stream) is transient", () => {
|
||||
const result = classifyProviderError("Expected double-quoted property name in JSON at position 23 (line 1 column 24)");
|
||||
assert.equal(result.isTransient, true, "truncated-stream JSON parse error should be transient");
|
||||
assert.equal(result.isRateLimit, false, "not a rate limit");
|
||||
assert.equal(result.suggestedDelayMs, 15_000, "should use 15s backoff like connection errors");
|
||||
});
|
||||
|
||||
test("#2572: 'Unexpected end of JSON input' (truncated stream) is transient", () => {
|
||||
const result = classifyProviderError("Unexpected end of JSON input");
|
||||
assert.equal(result.isTransient, true, "'Unexpected end of JSON input' should be transient");
|
||||
});
|
||||
|
||||
test("#2572: 'Unexpected token' in JSON (truncated stream) is transient", () => {
|
||||
const result = classifyProviderError("Unexpected token < in JSON at position 0");
|
||||
assert.equal(result.isTransient, true, "'Unexpected token in JSON' should be transient");
|
||||
});
|
||||
|
||||
test("#2572: 'SyntaxError' with JSON context (truncated stream) is transient", () => {
|
||||
const result = classifyProviderError("SyntaxError: JSON.parse: unexpected character at line 1 column 1");
|
||||
assert.equal(result.isTransient, true, "'SyntaxError...JSON' should be transient");
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue