[spark-compete] fix(codex): preserve operator next-move when codex binary is missing - #295
Conversation
Wraps subprocess.run in call_codex with FileNotFoundError + TimeoutExpired guards. Prevents a raw OSError stack trace from surfacing to the eval driver when the codex CLI is not installed or CODEX_PATH points at a removed binary; preserves the actionable next step (install codex / set CODEX_PATH) in the RuntimeError text. Closes the silent-hang window on timeout by surfacing the configured timeout_seconds in the raised message.
|
TL;DR — Driving the eval harness with the codex backend on a machine where the codex CLI isn't installed (or where CODEX_PATH points at a removed binary) currently surfaces a raw Repro (≤30s): Before: The probe-path helper One-file diff, +25/-6, no behaviour change on the happy path or on the existing |
…t stderr PR vibeforge1111#295 (adopt): wrap subprocess.run in try/except so a missing codex binary (FileNotFoundError) or an exceeded budget (TimeoutExpired) surface as an actionable RuntimeError that preserves the operator's next move, instead of a raw stack trace. PR vibeforge1111#296 (adjust): redact the raw stderr from the non-zero-exit RuntimeError (keep rc for triage, drop the stderr payload that can carry internal paths or prompt fragments). Per maintainer note, the tautological packet test (re-implemented the message, never imported codex_provider) was discarded; replaced with tests/test_codex_exec_stderr.py exercising the real call_codex redaction path via a monkeypatched subprocess.run. PR vibeforge1111#295 PR vibeforge1111#296 Co-authored-by: 4gjnbzb4zf-sudo@users.noreply.github.com Co-authored-by: TALLSOME24@users.noreply.github.com Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
{
"schema": "spark-compete-hotfix-v1",
"event": "spark-compete-first-event",
"submission_mode": "public_repo_pr",
"submission_target_url": "#295",
"team": {
"name": "SparkThisUp",
"members": [
"ValHallaBuilder",
"Baz707",
"DanFireDash"
],
"github_accounts": [
"4gjnbzb4zf-sudo"
],
"llm_device_holder": "ValHallaBuilder",
"device_holder_github": "4gjnbzb4zf-sudo"
},
"target_repo": {
"id": "vibeforge1111/spark-character",
"source": "https://github.com/vibeforge1111/spark-character",
"owner_surface": "spark-character"
},
"issue": {
"type": "usage_friction",
"severity": "low",
"title": "call_codex surfaces raw FileNotFoundError stack trace when codex binary is missing instead of an actionable RuntimeError",
"actual_behavior": "src/spark_character/codex_provider.py::call_codex invokes subprocess.run([s.binary, ...]) without wrapping the call. When the codex binary is missing from PATH (or CODEX_PATH points at a non-existent file), subprocess raises raw FileNotFoundError with a generic message that does not name the binary path or the CODEX_PATH override. The companion codex_available() probe path at line 93+ already catches the same exception class locally and returns False -- so the asymmetry exists on the call_codex hot path only.",
"expected_behavior": "call_codex catches FileNotFoundError around the codex subprocess.run call and re-raises as RuntimeError naming the binary path it tried and the CODEX_PATH env-var override (the two remediation surfaces the operator has). The codex_available probe path already swallows the same exception, so the asymmetry is closed without behavior change on successful exec.",
"repro_steps": [
"gh pr checkout ",
"Unset CODEX_PATH and ensure no
codexbinary is on PATH.","Trigger any spark-character path that calls call_codex (e.g. pipeline.generate with codex backend).",
"BEFORE: raw FileNotFoundError stack trace with [Errno 2] No such file or directory: 'codex'. AFTER: RuntimeError naming the binary path attempted and the CODEX_PATH override path."
],
"affected_workflow": "Operators onboarding spark-character with the codex backend (or switching providers) hit this when codex is not installed. The previous raw FileNotFoundError stack trace gave no path to inspect and no remediation; the new RuntimeError names both surfaces the operator can adjust."
},
"evidence": {
"safe_links_only": true,
"before_after_proof": "Single-file change in src/spark_character/codex_provider.py. The subprocess.run call inside call_codex is wrapped with try/except FileNotFoundError that re-raises as RuntimeError naming s.binary and a one-line operator next-step hint (install codex or set CODEX_PATH). Successful exec path unchanged.",
"links": [
"https://github.com//pull/295"
],
"forbidden": [
"pdf",
"zip",
"exe",
"unknown downloads",
"shortened links",
"archives",
"binaries",
"tokens",
"browser cookies",
"wallet material",
"raw logs",
"raw conversations",
"raw memory",
"raw patches",
"private repo maps",
"private scoring details"
]
},
"proposed_fix": {
"approach": "Wrap the subprocess.run call in call_codex with try/except FileNotFoundError. On catch, re-raise RuntimeError with a message that interpolates s.binary and names the CODEX_PATH override path. Same pattern as the existing codex_available() probe at line 93+ that catches the same exception class.",
"files_expected": [
"src/spark_character/codex_provider.py"
],
"tests_or_smoke": "python3 -m py_compile src/spark_character/codex_provider.py -> clean. Manual: run call_codex with no codex binary on PATH; observe the new RuntimeError names the binary + CODEX_PATH. Successful exec path unchanged."
},
"pr": {
"branch": "sentinel/ux-friction/codex-missing-binary-clear-error",
"title_prefix": "[spark-compete]",
"author_github": "4gjnbzb4zf-sudo",
"body_must_include": [
"packet",
"team",
"pr_author",
"repo",
"actual_behavior",
"expected_behavior",
"repro_steps",
"before_after_proof",
"tests_or_smoke",
"duplicate_notes",
"risk_notes",
"review_claim"
],
"url": "#295"
},
"review_claim": {
"impact_claim": "low",
"evidence_types": [
"redacted_terminal_excerpt"
],
"duplicate_notes": "Pre-flight check returned PR #295 area siblings: PR #89/#151/#163/#284 touch different files in spark-character. Sibling PR (this repo) for codex_provider.py out-path missing (different lines + different goal) ships as a separate parallel fix. No exact-line collision.",
"risk_notes": "Local scope: one try/except added around an existing subprocess call. Successful-exec branch unchanged. New RuntimeError uses the same exception class operators already see for other codex errors; no new package.",
"review_state_requested": "pr_review"
}
}