fix: improve codex exec error rendering (#18)

This commit is contained in:
banteg
2026-01-02 00:00:37 +04:00
committed by GitHub
parent 4aa7b74790
commit ac844b5305
3 changed files with 51 additions and 4 deletions
+3 -3
View File
@@ -409,7 +409,7 @@ async def handle_message(
if resume_token_value is None: if resume_token_value is None:
resume_token_value = progress_renderer.resume_token resume_token_value = progress_renderer.resume_token
progress_renderer.resume_token = resume_token_value progress_renderer.resume_token = resume_token_value
err_body = f"Error:\n{error}" err_body = str(error)
final_md = progress_renderer.render_final(elapsed, err_body, status="error") final_md = progress_renderer.render_final(elapsed, err_body, status="error")
logger.debug("[error] markdown: %s", final_md) logger.debug("[error] markdown: %s", final_md)
final_msg, edited = await _send_or_edit_markdown( final_msg, edited = await _send_or_edit_markdown(
@@ -463,9 +463,9 @@ async def handle_message(
final_answer = answer final_answer = answer
if run_ok is False and run_error: if run_ok is False and run_error:
if final_answer.strip(): if final_answer.strip():
final_answer = f"{final_answer}\n\nError:\n{run_error}" final_answer = f"{final_answer}\n\n{run_error}"
else: else:
final_answer = f"Error:\n{run_error}" final_answer = str(run_error)
status = ( status = (
"error" if run_ok is False else ("done" if final_answer.strip() else "error") "error" if run_ok is False else ("done" if final_answer.strip() else "error")
+23
View File
@@ -39,6 +39,25 @@ _ACTION_KIND_MAP: dict[str, ActionKind] = {
} }
_RESUME_RE = re.compile(r"(?im)^\s*`?codex\s+resume\s+(?P<token>[^`\s]+)`?\s*$") _RESUME_RE = re.compile(r"(?im)^\s*`?codex\s+resume\s+(?P<token>[^`\s]+)`?\s*$")
_ANSI_ESCAPE_RE = re.compile(r"\x1b\[[0-9;]*[A-Za-z]")
_TRUSTED_DIR_RE = re.compile(r"not inside a trusted directory", re.IGNORECASE)
def _strip_ansi(text: str) -> str:
return _ANSI_ESCAPE_RE.sub("", text)
def _extract_stderr_reason(stderr_tail: str) -> str | None:
if not stderr_tail:
return None
cleaned = _strip_ansi(stderr_tail)
lines = [line.strip() for line in cleaned.splitlines() if line.strip()]
if not lines:
return None
for line in lines:
if _TRUSTED_DIR_RE.search(line):
return line
return lines[-1]
def _started_event(token: ResumeToken, *, title: str) -> StartedEvent: def _started_event(token: ResumeToken, *, title: str) -> StartedEvent:
@@ -558,6 +577,10 @@ class CodexRunner(ResumeTokenMixin, JsonlSubprocessRunner):
stderr_tail: str, stderr_tail: str,
state: CodexRunState, state: CodexRunState,
) -> list[TakopiEvent]: ) -> list[TakopiEvent]:
reason = _extract_stderr_reason(stderr_tail)
if reason:
message = f"codex exec failed (rc={rc}).\n\n{reason}"
else:
message = f"codex exec failed (rc={rc})." message = f"codex exec failed (rc={rc})."
resume_for_completed = found_session or resume resume_for_completed = found_session or resume
return [ return [
+24
View File
@@ -243,6 +243,30 @@ async def test_codex_runner_preserves_warning_order(tmp_path) -> None:
assert seen[2].answer == "ok" assert seen[2].answer == "ok"
@pytest.mark.anyio
async def test_codex_runner_includes_stderr_reason(tmp_path) -> None:
codex_path = tmp_path / "codex"
codex_path.write_text(
"#!/usr/bin/env python3\n"
"import sys\n"
"\n"
"sys.stderr.write('Not inside a trusted directory and --skip-git-repo-check was not specified.\\n')\n"
"sys.stderr.flush()\n"
"sys.exit(1)\n",
encoding="utf-8",
)
codex_path.chmod(0o755)
runner = CodexRunner(codex_cmd=str(codex_path), extra_args=[])
events = [evt async for evt in runner.run("hi", None)]
completed = next(evt for evt in events if isinstance(evt, CompletedEvent))
assert completed.ok is False
assert completed.error is not None
assert "codex exec failed (rc=1)." in completed.error
assert "\n\nNot inside a trusted directory" in completed.error
@pytest.mark.anyio @pytest.mark.anyio
async def test_run_serializes_two_new_sessions_same_thread( async def test_run_serializes_two_new_sessions_same_thread(
tmp_path, monkeypatch tmp_path, monkeypatch