Skip to content

A server-side turn failure that terminates stores no errorMessage, so status reports the reason as Summary: { #757

Description

@aleozlx

Summary

A task turn that fails server-side and terminates normally leaves no reason anywhere in
status. The job is correctly marked failed, but the only diagnostic the report carries is

Summary: {

— a bare opening brace: the first line of the pretty-printed JSON error body. The real reason
is one command away in result <job-id>, but nothing in the status output says so, and {
reads as a rendering glitch rather than as an error.

This is distinct from #698. There the error notification is non-terminal and the job
wedges at status: running forever. Here the turn does complete: the job reaches
status: failed with phase: failed, pid: null and completedAt set. The failure is
legible in the job log and in result — everywhere except the command a caller runs first.

Fixing #698 would not fix this. Making the error notification terminal routes the turn into
the same non-throwing completion path described below, where the summary is still { and
errorMessage is still never written.

Root cause — two independent gaps that meet

1. The summary is the first line of the raw output.

plugins/codex/scripts/codex-companion.mjs:525:

summary: firstMeaningfulLine(rawOutput, firstMeaningfulLine(failureMessage, `${taskMetadata.title} finished.`)),

firstMeaningfulLine (:174) returns the first non-blank trimmed line. On a failed turn
rawOutput is the pretty-printed error body, so that line is the opening {. Note that
shorten() (lib/codex.mjs:90) does collapse whitespace, but it is not on this path.

2. errorMessage is only written on the throwing path.

plugins/codex/scripts/lib/tracked-jobs.mjs writes errorMessage only inside the catch
(:182, :189, :199). A turn that fails server-side but completes returns normally, so the
success branch runs (:174, summary: execution.summary) and no errorMessage is ever
stored.

renderJobStatusReport already has the branch that would print it —
lib/render.mjs:437-440 — and on this path it is dead, falling through to
"No captured result payload was stored for this job."

Reproduction

Any 400 from the turn reproduces this; the trigger below is incidental. I used an effort value
the current model rejects (that vocabulary drift is #751 — not what this issue is about).

node scripts/codex-companion.mjs task --background --fresh --effort minimal "Reply with exactly: OK"

status — what a caller sees first

# Codex Job Status

- task-mtyt7j74-roqc7q | failed | rescue | Codex Task
  Summary: {
  Phase: failed
  Duration: 3s
  ...
  Progress:
    Thread ready (...).
    Turn started (...).
    Codex error: {
    Turn failed.

Both the Summary: field and the Codex error: progress line are cut to {.

The stored index row

{"id":"task-mtyt7j74-roqc7q","status":"failed","summary":"{"}

errorMessage is absent from both the index entry and the job file. The job file's keys are
id, kind, kindLabel, title, workspaceRoot, jobClass, summary, write, createdAt, status, phase, pid, logFile, request, startedAt, threadId, turnId, completedAt, result, rendered — no
errorMessage.

result — where the reason actually is

{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "code": "unsupported_value",
    "message": "Unsupported value: 'minimal' is not supported with the 'gpt-5.6-terra' model. Supported values are: 'none', 'low', 'medium', 'high', 'xhigh', and 'max'.",
    "param": "reasoning.effort"
  },
  "status": 400
}

Complete and diagnostic — reached only by knowing to ask for it.

Why this is worth more than a cosmetic fix

The failure is a 400 on the first turn, about four seconds in, after the thread has already
reported ready. An agent that delegates to Codex, sees failed, and does not go on to open
result or the log concludes that Codex is broken or unavailable on that machine. That is the
wrong conclusion, and it is the one the output steers a reader toward: a summary of { looks
like the tool is malfunctioning, not like the request was rejected for a stated reason.

It costs a person the same way. status is the documented next step after a background task,
and it is the one view that currently cannot say what went wrong.

Suggested fix

Either half alone would make the failure legible; both are small.

  • Set errorMessage on the non-throwing failure path, from result.error.message when the
    turn completed with a failed status. The renderer already prints it.
  • Make the summary robust to a structured body: prefer error.message when the payload parses
    as an error, and otherwise put firstMeaningfulLine through the same whitespace collapsing
    shorten() already does, so a multi-line value can never reduce to punctuation.

The same { truncation affects the Codex error: progress line, which comes from
lib/codex.mjs:539 where message.params.error.message is interpolated raw and not shortened.

Environment

  • Plugin codex v1.0.5 as installed; every line cited re-checked against main @ v1.0.6 and
    unchanged, at identical line numbers
  • codex-cli 0.153.4
  • Windows 11 (26200), Node v24.19.0, ChatGPT sign-in
  • Model gpt-5.6-terra (pinned in ~/.codex/config.toml by the Codex desktop app)

Related: #698 (the non-terminating variant of a Codex-side failure), #695 (failure reason
discarded on the stop-gate paths), #751 (the effort vocabulary drift that made a convenient
trigger here).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions