Shell wrapper hardcoded causes legitimate exit code 1 (grep no-match) to abort tasks

Open 💬 3 comments Opened May 23, 2026 by MayerZhe

No description provided.

View original on GitHub ↗

3 Comments

jshaofa-ui · 3 months ago

Proposed Solution: POSIX-Compliant Exit Code Handling

Root Cause

Shell wrapper treats exit code 1 as fatal error. But exit code 1 is used for many legitimate conditions: grep (no matches), diff (files differ), test (condition false). Wrapper should only treat exit code >= 2 as fatal.

Fix

// Before: if (exitCode !== 0) throw Error
// After: if (exitCode >= 2) throw Error  // 0-1 = pass through
  • Short-term: Exit code threshold adjustment (>= 2 = fatal)
  • Medium-term: Per-command exit code semantics (grep: 1=no match)
  • Long-term: Full POSIX exit code semantics with agent-visible exit code

Testing

  • grep "nonexistent" file — should return exit code 1, not abort
  • diff file1 file2 (different) — should return exit code 1
  • command_not_found — should return exit code 127

Full solution: solutions/codex-24226-shell-wrapper-exit-code-1-abort-fix.md

jshaofa-ui · 3 months ago

Proposed Solution: Shell Wrapper Exit Code 1 Aborts Tasks

Root Cause

Shell wrapper treats ALL non-zero exit codes as fatal. But many standard Unix commands use exit code 1 for legitimate non-error conditions: grep (no match), diff (files differ), test/[ (condition false), sort -c (unsorted).

Fix

Exit code classification:

const LEGITIMATE_EXIT_CODES = {
  'grep': new Set([0, 1, 2]),
  'diff': new Set([0, 1, 2]),
  'test': new Set([0, 1]),
  '[': new Set([0, 1]),
};

function isLegitimateExitCode(cmd, code) {
  const allowed = LEGITIMATE_EXIT_CODES[cmd.split(' ')[0]];
  return allowed ? allowed.has(code) : code === 0;
}

Full solution: codex-24226-shell-wrapper-exit-code-1-fix.md

yui-stingray · 3 months ago

I looked at current openai/codex@7d47056. The part that seems most relevant to the grep no-match case is not only the wrapper shape, but the result mapping after the command returns.

codex-rs/core/src/tasks/user_shell.rs and codex-rs/core/src/tools/events.rs both appear to classify exit_code == 0 as the only successful shell/tool result, so a legitimate grep no-match 1 is still routed through the failure path. codex-rs/core/src/exec.rs does not appear to special-case 1; its quick sandbox-denial check is for 2, 126, and 127.

I would avoid treating every exit code 1 as success globally, because many commands use it for real failures. A safer direction would be to keep the raw exit code visible, but separate "command failed" from "task should abort", or add command/context-specific expected-exit handling for known cases like grep no-match, diff differences, and test false.