fix(resilience): isolate local host execution errors from provider circuit breakers (#12233)

Local process execution failures (ENOENT spawn errors, binary missing, EPIPE, exit codes) were incorrectly treated as upstream provider failures, opening provider circuit breakers and cooling down valid connections. Added `isLocalExecutionError` guard to skip circuit breaker trips and connection disables when local host execution fails.
This commit is contained in:
Syed Raheemuddin
2026-09-01 09:17:21 +05:30
committed by GitHub
parent 26eeead268
commit ae37413aff
5 changed files with 109 additions and 3 deletions

View File

@@ -65,6 +65,43 @@ export function isLocalStreamLifecycleError(error: unknown): boolean {
);
}
const LOCAL_EXECUTION_CODES = new Set([
"ENOENT",
"EACCES",
"EPIPE",
"ERR_CHILD_PROCESS_STDIO_MAXBUFFER",
]);
const LOCAL_EXECUTION_PATTERNS = [
/\bspawn\b.*\b(ENOENT|EACCES|EPIPE)\b/i,
/\bcommand not found\b/i,
/\bis not recognized as an internal or external command\b/i,
/\bchild process exited with code\b/i,
/\blocal host execution error\b/i,
];
/**
* Detect a LOCAL host execution error (missing binary ENOENT, permission EACCES,
* broken pipe EPIPE, child process exit errors, etc.) that must NOT count as a
* whole-provider failure or trip remote provider circuit breakers.
*/
export function isLocalExecutionError(error: unknown): boolean {
if (!error) return false;
const errObj = typeof error === "object" ? (error as Record<string, unknown>) : null;
const code = typeof errObj?.code === "string" ? errObj.code : "";
if (LOCAL_EXECUTION_CODES.has(code)) return true;
const message =
typeof error === "string"
? error
: typeof errObj?.message === "string"
? (errObj.message as string)
: "";
if (!message) return false;
return LOCAL_EXECUTION_PATTERNS.some((p) => p.test(message));
}
export const STATE = {
CLOSED: "CLOSED",
DEGRADED: "DEGRADED",

View File

@@ -1,4 +1,7 @@
import { isLocalStreamLifecycleError } from "../../shared/utils/circuitBreaker";
import {
isLocalStreamLifecycleError,
isLocalExecutionError,
} from "../../shared/utils/circuitBreaker";
import { isRequestScopedUpstreamFailure } from "./comboFailureLogging";
import { getTrustedLocalRateLimitResponse } from "@omniroute/open-sse/services/rateLimitManager/errors";
@@ -29,6 +32,7 @@ export function shouldTripProviderBreakerForResult(
!isRequestScopedUpstreamFailure({ code: result.errorCode, type: result.errorType }) &&
!(result.response && getTrustedLocalRateLimitResponse(result.response)) &&
!isLocalStreamLifecycleError(result.error) &&
!isLocalExecutionError(result.error) &&
// Network-layer errors (ECONNREFUSED, ETIMEDOUT) never reached the provider —
// the provider may be healthy, only the network path is broken. OmniRoute's own
// rate-limit queue timeouts are backpressure we applied, not a provider failure.