fix(exec): treat shell exit codes 126/127 as failures instead of completed

When a command exits with code 127 (command not found) or 126 (not
executable), the exec tool previously returned status "completed" with
the error buried in the output text. This caused cron jobs to report
status "ok" and never increment consecutiveErrors, silently swallowing
failures like `python: command not found` across multiple daily cycles.

Now these shell-reserved exit codes are classified as "failed", which
propagates through the cron pipeline to properly increment
consecutiveErrors and surface the issue for operator attention.

Fixes #24587

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
SidQin-cyber
2026-02-24 11:58:52 +08:00
parent 853ae626fa
commit 2b1d1985ef

View File

@@ -482,7 +482,13 @@ export async function runExecProcess(opts: {
.then((exit): ExecProcessOutcome => {
const durationMs = Date.now() - startedAt;
const isNormalExit = exit.reason === "exit";
const status: "completed" | "failed" = isNormalExit ? "completed" : "failed";
const exitCode = exit.exitCode ?? 0;
// Shell exit codes 126 (not executable) and 127 (command not found) are
// unrecoverable infrastructure failures that should surface as real errors
// rather than silently completing — e.g. `python: command not found`.
const isShellFailure = exitCode === 126 || exitCode === 127;
const status: "completed" | "failed" =
isNormalExit && !isShellFailure ? "completed" : "failed";
markExited(session, exit.exitCode, exit.exitSignal, status);
maybeNotifyOnExit(session, status);
@@ -491,7 +497,6 @@ export async function runExecProcess(opts: {
}
const aggregated = session.aggregated.trim();
if (status === "completed") {
const exitCode = exit.exitCode ?? 0;
const exitMsg = exitCode !== 0 ? `\n\n(Command exited with code ${exitCode})` : "";
return {
status: "completed",
@@ -502,8 +507,11 @@ export async function runExecProcess(opts: {
timedOut: false,
};
}
const reason =
exit.reason === "overall-timeout"
const reason = isShellFailure
? exitCode === 127
? "Command not found"
: "Command not executable (permission denied)"
: exit.reason === "overall-timeout"
? `Command timed out after ${opts.timeoutSec} seconds`
: exit.reason === "no-output-timeout"
? "Command timed out waiting for output"