Skip to content

fix(ci): salvage Codex review output on PTY-shutdown hang - #7042

Merged
wpfleger96 merged 4 commits into
mainfrom
duncan/codex-review-salvage
Aug 31, 2026
Merged

fix(ci): salvage Codex review output on PTY-shutdown hang#7042
wpfleger96 merged 4 commits into
mainfrom
duncan/codex-review-salvage

Conversation

@wpfleger96

@wpfleger96 wpfleger96 commented Aug 29, 2026

Copy link
Copy Markdown
Member

Codex CLI can leave a PTY descendant holding the action's inherited stdio after the turn completes. The runCodexExec.ts wrapper waits on a close event that never fires, so the Review pull request step hangs until the job timeout kills it — discarding the finished review the CLI already wrote to disk.

The CLI writes the completed review to the --output-last-message file (exposed as output-file) before the hang. This PR adds a salvage step that recovers it, and sets the step and job timeouts to preserve the full 30-minute Codex execution budget.

Changes (codex-security-review.yml):

  • Add output-file: ${{ runner.temp }}/codex-review.json to the Review pull request step so the CLI writes the result before the hang. (runner context is valid in steps.with; not in jobs.env.)
  • Add timeout-minutes: 30 and continue-on-error: true to the Codex step — a hang now costs ≤30 minutes instead of 40, and the salvage step still runs.
  • Set job timeout-minutes: 40 to give setup, step cancellation, and salvage sufficient headroom without colliding with the Codex execution budget. The original 30-minute job timeout was too narrow: evidence from run 33114428326 shows completed output appearing 28m46s after step start, meaning a 20-minute step timeout could kill a legitimate review before the salvage file exists.
  • Add a Salvage review output step with if: always(): prefers steps.run_codex.outputs.final-message on a clean exit; falls back to the output file when the step timed out. The output file path is set in the step's own env block (CODEX_OUTPUT_FILE: ${{ runner.temp }}/codex-review.json), where runner is valid. Validates shape (non-empty JSON object, has overall_risk); fails the job hard if neither source is present.
  • Wire the job outputs.review_json to steps.salvage.outputs.review_json.

Changes (Justfile, ci.yml):

  • Add actionlint .github/workflows/codex-security-review.yml to security-review-check so expression-validity errors are caught locally.
  • Provision actionlint via Hermit (pinned v1.7.12) rather than a one-off Install actionlint curl step, so the same binary is used locally and in CI.

Security posture is unchanged: the salvage step reads the action's own output and a file written to runner.temp — neither is PR-controlled. Credential-stripping env block on the Codex step is untouched.

Note this is a temporary workaround until openai/codex-action#169 is addressed

Codex CLI leaves a PTY descendant holding the action's inherited stdio
after the turn completes; the action wrapper waits on a `close` event
that never fires, so the job hangs until timeout-minutes kills it. The
CLI writes the finished review to the output file before the hang, so
the result is always present on disk — the job just never reads it.

Add a salvage step that runs with `if: always()` after the Codex step:
prefer the action's `final-message` output on a clean exit; fall back
to the output file when the step timed out. Shape-validate the recovered
JSON (non-empty object, has `overall_risk`); fail hard if neither source
is present. Set `timeout-minutes: 20` and `continue-on-error: true` on
the Codex step so a hang costs 20 minutes instead of 30 and the salvage
step still runs.

The output file is written to `runner.temp` (outside both checkouts)
and the salvage step receives the action output as an env var, not from
the PR-controlled tree, preserving the existing isolation posture.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96
wpfleger96 requested a review from a team as a code owner August 29, 2026 20:29
`runner` context is not allowed in `jobs.<job_id>.env`; the merged
workflow would be rejected at load time, disabling the security review.
Move `runner.temp` to the two step-level locations where it is valid:
the Codex step's `output-file` input (inside `with`) and the salvage
step's own `env` block. While here, fix a SC2129 shellcheck style
warning in the salvage script by grouping the GITHUB_OUTPUT writes.

Add `actionlint .github/workflows/codex-security-review.yml` to the
`security-review-check` Justfile recipe so expression-validity errors
are caught locally and in CI. Wire a pinned actionlint install
(v1.7.12, SHA-256 verified) into the `changes` job in ci.yml
immediately before the `just security-review-check` step.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The previous approach installed actionlint with a custom curl/checksum/tar
step in ci.yml, which kept the binary outside the Hermit environment. This
made `just security-review-check` fail with `command not found` on any
fresh checkout using the documented Hermit-only PATH (`just ci` / `just
bootstrap` paths), while CI stayed green only because the custom install
step masked the gap.

Track actionlint-1.7.12 in Hermit so both local and CI environments get the
same pinned binary through the same provisioning path. Remove the six-line
curl/checksum/tar install step from the ci.yml contracts job; cashapp/
activate-hermit already runs there and will lazily provision the binary.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The 20-minute step timeout was below the only bounded timing evidence for
a real heavy review: output appeared at 28m46s after step start in run
33114428326. A legitimate review could be killed before the salvage file
is written, discarding a valid result.

Separate the step and job deadlines to preserve the 30-minute Codex
execution budget while still leaving headroom for setup, step
cancellation, and salvage to complete within the job ceiling.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96
wpfleger96 merged commit 17ecf0b into main Aug 31, 2026
34 of 35 checks passed
@wpfleger96
wpfleger96 deleted the duncan/codex-review-salvage branch August 31, 2026 17:31
wpfleger96 pushed a commit that referenced this pull request Aug 31, 2026
* origin/main:
  feat: render agent avatars as squircles (#7106)
  fix(ci): salvage Codex review output on PTY-shutdown hang (#7042)
  fix: retrieving cold memories; add regression task (#6950)
  Enforce NIP-OA authorization time bounds (#7004)
  feat(db): configurable writer session timeouts (lock, idle-txn, statement) (#6229)
  feat(desktop): use segmented controls for channel creation (#6845)
  feat(buzz-agent): surface stop reason and silent-turn WARN in telemetry (#7038)
  fix(desktop): surface channel history load failures (#7013)
  fix(composer): polish automatic mentions (#6956)

Signed-off-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
jrobotham-square added a commit that referenced this pull request Aug 31, 2026
…age-rw

* origin/main:
  feat: render agent avatars as squircles (#7106)
  fix(ci): salvage Codex review output on PTY-shutdown hang (#7042)
  fix: retrieving cold memories; add regression task (#6950)
  Enforce NIP-OA authorization time bounds (#7004)
  feat(db): configurable writer session timeouts (lock, idle-txn, statement) (#6229)
  feat(desktop): use segmented controls for channel creation (#6845)
  feat(buzz-agent): surface stop reason and silent-turn WARN in telemetry (#7038)
  fix(desktop): surface channel history load failures (#7013)
  fix(composer): polish automatic mentions (#6956)

Signed-off-by: Joel Robotham <jrobotham@squareup.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants