fix(review): surface codex's human error, not the raw JSON blob · Entire
fix(review): surface codex's human error, not the raw JSON blob
5a2c8e8·
peyton-alt·3d ago·2 files·+73 added/-1 removed
When codex fails, it wraps the upstream API error as a JSON string in its envelope message, so a failed reviewer row showed the whole blob (e.g. codex: {"type":"error","status":400,"error":{"message":"The 'gpt-5.6-sol' model requires a newer version of Codex..."}}) — the actual reason buried inside. cleanCodexFailureMessage unwraps it to the .error.message text so the row reads "codex: The 'gpt-5.6-sol' model requires a newer version of Codex...". Plain (non-JSON) messages and JSON without a message pass through unchanged; unwrapping is bounded against multiple nesting. Found while diagnosing a live codex failure (model/CLI-version mismatch) that the raw blob made hard to read.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Sessions
01KXH8Z4S6VJF3PFYBW4V44WENView transcript
Changes
2
- cmd/entire/cli/agent/codex
- Mreviewer.go+34/-1
- Mreviewer_test.go+39
166 unmodified lines
// bare "exit status 1". Only emitted below if the turn never
// completes, so a stray message on a successful run is ignored.
if msg := strings.TrimSpace(firstNonEmptyString(env.Error.Message, env.Message)); msg != "" {
failureMsg = msg
failureMsg = cleanCodexFailureMessage(msg)
}
// Add cases here when codex's envelope or item types grow; the
// default arm logs unknown types at Debug so drift can be
118 unmodified lines
Message string `json:"message"`;
}
// cleanCodexFailureMessage unwraps a codex failure message that is itself a
// JSON-encoded API error, returning the human-readable text at `.error.message`
// (or a top-level `.message`) instead of the raw blob. Codex surfaces upstream
// API errors this way — e.g. a model/version mismatch arrives as
// `{\"type\":\"error\",\"status\":400,\"error\":{\"message\":\"The '…' model requires a
// newer version of Codex…\"}}`. A plain (non-JSON) message, or JSON without a
// message field, passes through unchanged. Unwrapping is bounded in case the
// message is nested more than once.
func cleanCodexFailureMessage(msg string) string {
current := strings.TrimSpace(msg)
for range 3 {
trimmed := strings.TrimSpace(current)
if !strings.HasPrefix(trimmed, "{") {
return current
}
var wrapped struct {
Error struct {
Message string `json:"message"`;
} `json:"error"`;
Message string `json:"message"`;
}
if err := json.Unmarshal([]byte(trimmed), &wrapped); err != nil {
return current
}
inner := firstNonEmptyString(wrapped.Error.Message, wrapped.Message)
if strings.TrimSpace(inner) == "" {
return current
}
current = strings.TrimSpace(inner)
}
return current
}
func firstNonEmptyString(values ...string) string {
for _, v := range values {
if v != "" {