Skip to content

Runner & adapter resilience (review/generic/codex) - #370

Merged
andrewklingelhofer merged 3 commits into
mainfrom
fix/audit-runner-resilience
Jul 18, 2026
Merged

Runner & adapter resilience (review/generic/codex)#370
andrewklingelhofer merged 3 commits into
mainfrom
fix/audit-runner-resilience

Conversation

@andrewklingelhofer

Copy link
Copy Markdown
Contributor

Summary

Harden the phase runners against trusting agent output and IO races.

Area Problem Fix
Review merge Trusted a hallucinated REVIEW_RESULT:MERGED sentinel Confirm gh pr view <n> --json state == MERGED before applying the merged outcome; else downgrade
Review checkout PR-branch checkout ignored gh failure → silently reviewed the base branch (and could approve/merge) Check the exit code and fail loudly
Codex extractFinalResponse read the last-message file without try/catch Wrap and fall through to the log/unknown
Generic Empty .critter-report.md suppressed the stream-json fallback Treat empty/whitespace as missing and fall through
Reduction Dead parseReviewOutcome (bypassed the adapter abstraction) Removed; tests repointed to cliAdapter.extractReviewDecision; redundant double gh pr view deduped

Tests

audit-runners.test.ts — merged-confirmation gate and empty-report fallback (gh/file IO mocked).

Verification

typecheck clean · lint exit 0 · bun test 849 pass / 0 fail.

🤖 Generated with Claude Code

andrewklingelhofer and others added 3 commits June 15, 2026 21:50
Audit fixes for runner & adapter resilience:

- review.ts: confirm a claimed REVIEW_RESULT:MERGED decision against
  `gh pr view <n> --json state` before applying the terminal merged
  outcome; downgrade to "unknown" (task fails loudly) when the PR is not
  actually merged, so a hallucinated sentinel can't move an issue to Done
  with an open PR.
- review.ts: fail loudly when PR-branch resolution returns a non-zero
  exit code or empty headRefName, instead of silently reviewing (and
  potentially approving/merging) the BASE branch.
- cli/codex.ts: wrap the lastMessageFile read in extractFinalResponse in
  try/catch so an IO race falls through to the stream-json log instead of
  throwing out of result handling.
- runner/generic.ts: treat an empty/whitespace-only .critter-report.md as
  missing and fall through to the stream-json fallback.

Adds src/__tests__/audit-runners.test.ts covering the merged-confirmation
gate (downgrade + confirmed-merge + branch-resolution failure), the
generic empty-report fallback, and codex IO resilience.

Deferred: removing parseReviewOutcome is left out because the non-owned
test src/__tests__/review-spawner.test.ts imports it.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Production review parsing already flows through
ctx.cliAdapter.extractReviewDecision; drop the thin parseReviewOutcome
wrapper and its export, and repoint its tests to exercise the adapter
directly. Also collapse the merged-confirmation downgrade and the
no-sentinel fallback into a single `gh pr view --json state` query since
both inspect the identical post-spawn PR state.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
resolvePhaseMcpConfig expands ~ via node:os homedir(); the test compared it to
process.env.HOME. Bun caches homedir() on first call and ignores later
process.env.HOME mutations, so when a sibling test changes HOME they diverge on
Linux CI and this test fails (works on macOS by execution-order luck). Assert
against the same homedir() the implementation uses.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@andrewklingelhofer
andrewklingelhofer merged commit 063f92f into main Jul 18, 2026
1 check passed
@andrewklingelhofer
andrewklingelhofer deleted the fix/audit-runner-resilience branch July 18, 2026 17:57
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.

1 participant