fix(codex): propagate failed app-server turns - #1599
Open
AaronZ345 wants to merge 1 commit into
Open
Conversation
AaronZ345
force-pushed
the
fix/codex-appserver-turn-error-20260725
branch
2 times, most recently
from
August 7, 2026 14:41
6f01703 to
a7419d0
Compare
AaronZ345
force-pushed
the
fix/codex-appserver-turn-error-20260725
branch
2 times, most recently
from
August 14, 2026 14:43
c0fabf8 to
a54972b
Compare
chenhg5
approved these changes
Aug 15, 2026
chenhg5
left a comment
Owner
There was a problem hiding this comment.
Conclusion: Approve
Overall assessment:
Codex app-server's turn/completed notification carries both a status field ("completed" / "failed") and an optional error object. The previous adapter ignored both and always emitted a successful EventResult — so failed turns surfaced to users as (empty response) and to scheduled-job logs as "completed". This PR inspects both fields and routes a failed turn to EventError via a new failTurn helper that mirrors the existing completeTurn lifecycle. The fix is small (24 production lines), focused, and well-tested. Local go test -race ./agent/codex/... is green.
Review scope:
- Read
agent/codex/appserver_session.go(handleNotificationforturn/completed, the newfailTurn, and the existingcompleteTurnfor comparison), and the newTestAppServerSession_FailedTurnEmitsErrortest. - Verified that
emitErroralready emitscore.Event{Type: core.EventError, Error: err}and that downstream consumers handleEventError.
✅ What looks good:
- The dual-trigger check (
status == "failed"ORturn.error != nil) catches both ways Codex can report a failed turn: explicit failure status and a status of "completed" with an attached error object. Either path now producesEventErrorinstead of a deceptiveEventResult. failTurnreuses the samestateMuguard ascompleteTurn: thecurrentTurn == ""early-return makes it idempotent and race-safe against a delayedthread/status/changedidle notification. The new test asserts exactly this — the second notification must NOT emit a duplicate event.- Error message fallback to
"turn failed (no details)"keeps the surface stable even if Codex sendsstatus="failed"without anerrorpayload. - Minimal blast radius: only
turn/completedis touched; everything else (item lifecycle, rate limits, usage updates) is unchanged.
🟠 Should improve:
- Ordering race (worth a follow-up issue, not blocking): if
thread/status/changed(idle) arrives BEFOREturn/completed(failed), the existingcompleteTurnwill emit a successfulEventResultand clearcurrentTurn; the subsequentfailTurnthen no-ops and the failure is silently lost. The Codex app-server contract saysturn/completedprecedesidle, but degraded network conditions can reorder them. A defensive mitigation would be to remember the last terminal status for the current turn (e.g.lastTurnStatus) and overwrite an already-emittedEventResultif a later notification disagrees — or at minimum, log a warning whenfailTurnfindscurrentTurn == "". Either approach is small and contained. - Test coverage gaps: the new test only covers
status="failed"with an error message. Worth adding cases for (a)status="failed"witherror == nil(verifies the"turn failed (no details)"fallback), (b)status="completed"witherror != nil(verifies the||branch), and (c) acancelled-style status to document current behavior (currently treated as success — may be intentional, but should be a conscious choice). - Multi-line error messages:
strings.TrimSpace(notif.Turn.Error.Message)keeps only the first line if Codex sends stack-trace-style messages. If the message can contain newlines (likely forinternal_error), consider passing through the full message or splitting on first newline for a "summary + detail" pattern.
🔵 Optional:
- The condition
strings.EqualFold(strings.TrimSpace(notif.Turn.Status), "failed")is a tiny bit defensive — if the SDK already lowercases or trims, both calls are free, but it's worth a comment noting why we re-do it. Alternatively, capturestatus := strings.ToLower(strings.TrimSpace(notif.Turn.Status))once at the top of the case. - A
slog.WarninsidefailTurn(withturn_idanderror) would help post-mortem correlation in scheduled-job failure logs.
❓ Questions:
- Are there any downstream consumers of
EventResultthat should ALSO subscribe toEventErrorto handle the new failure path correctly? In particular, scheduled-job wrappers that compute "did this cron run succeed?" from the event stream. A quickgrep -rn "EventResult\|EventError" --include="*.go"on the cron path would close the loop.
Testing / Risk:
- Verified:
go test -race ./agent/codex/...is green.TestAppServerSession_FailedTurnEmitsErrorpasses, including the post-failure idle-doesn't-emit-duplicate assertion. - Unverified risk: the ordering race described above (idle before failed). Acceptable for a first fix; capture in a follow-up issue.
Next step:
- Land as-is. File a follow-up issue for the ordering-race mitigation and the additional test cases.
AaronZ345
force-pushed
the
fix/codex-appserver-turn-error-20260725
branch
from
August 15, 2026 14:44
a54972b to
4cad450
Compare
AaronZ345
force-pushed
the
fix/codex-appserver-turn-error-20260725
branch
from
August 16, 2026 14:44
4cad450 to
7b2df44
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
turn/completedstatus and error data from Codex app-serverEventErrorinstead of a successful emptyEventResultwhen the turn failedWhy
Codex app-server can report a failed turn through
turn/completedwithturn.status = "failed"and an error object. The adapter currently ignores both fields and always completes successfully. Scheduled jobs then render(empty response)and are recorded as completed, hiding the upstream error.Tests
go test ./agent/codexgit diff --check