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

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 != "" {