From 62af044d3d7819dc983d9bb731235264c0c9f8ef Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 5 Jul 2026 21:57:36 +0900 Subject: [PATCH 1/2] fix(review): validate model output on a copy so publish re-normalize succeeds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the pool-cycling fix. opencode_review_normalize_output.py rewrites its input in place and is NOT idempotent, and the publish step normalizes the selected output again. The pool was normalizing (mutating) the very file it then handed to the publish step, so a model whose FIRST normalize passed (recorded as the pool's success) failed the publish step's SECOND normalize — ending the run in "Selected successful OpenCode output did not include a valid control conclusion" instead of the review completing. Normalize/approve-gate a throwaway ANSI-stripped copy and leave the model output pristine, so the publish step performs the one-and-only normalize of that content and its result matches the pool's decision. Verified locally against a non-idempotent normalizer stub: pool passes, output stays pristine, publish normalize passes. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01RTAMs4bpSZS77Xe3RQjv9P --- scripts/ci/run_opencode_review_model_pool.sh | 38 ++++++++++---------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/scripts/ci/run_opencode_review_model_pool.sh b/scripts/ci/run_opencode_review_model_pool.sh index 120c955..6cdf1b8 100644 --- a/scripts/ci/run_opencode_review_model_pool.sh +++ b/scripts/ci/run_opencode_review_model_pool.sh @@ -14,28 +14,30 @@ record_review_model() { normalize_opencode_output() { local output_file="$1" - # Strip terminal escape sequences in place first, so the pool validates the - # exact bytes the publish step re-validates (it ANSI-strips with the same - # regex before running the normalizer). Without this the pool could accept a - # model whose raw output passes but whose ANSI-stripped form the publish step - # rejects, ending the run in failure instead of falling through to the next - # model in the pool. - local stripped - stripped="$(mktemp)" - if perl -pe 's/\x1b\[[0-9;?]*[A-Za-z]//g' "$output_file" >"$stripped" 2>/dev/null; then - mv "$stripped" "$output_file" - else - rm -f "$stripped" - fi + # Validate a throwaway copy, never the file itself. The publish step runs + # opencode_review_normalize_output.py on the model output, and that script + # REWRITES its input in place (it is not idempotent). If the pool normalized + # output_file directly, the publish step would normalize the already-rewritten + # content a second time and fail with "Selected successful OpenCode output did + # not include a valid control conclusion", ending the run instead of falling + # through to the next model. Mirror the publish step exactly — ANSI-strip a + # copy, then normalize — so the pool only records success for output the + # publish step will accept, and leave output_file pristine for the publish + # step to normalize itself. + local probe rc + probe="$(mktemp)" + perl -pe 's/\x1b\[[0-9;?]*[A-Za-z]//g' "$output_file" >"$probe" 2>/dev/null || cp "$output_file" "$probe" if python3 "$GITHUB_WORKSPACE/scripts/ci/opencode_review_normalize_output.py" \ - "$HEAD_SHA" "$RUN_ID" "$RUN_ATTEMPT" "$output_file"; then + "$HEAD_SHA" "$RUN_ID" "$RUN_ATTEMPT" "$probe"; then bash "$GITHUB_WORKSPACE/scripts/ci/opencode_review_approve_gate.sh" \ - "$HEAD_SHA" "$RUN_ID" "$RUN_ATTEMPT" "$output_file" >/dev/null - return $? + "$HEAD_SHA" "$RUN_ID" "$RUN_ATTEMPT" "$probe" >/dev/null + rc=$? + else + rc=1 fi - - return 1 + rm -f "$probe" + return "$rc" } backoff_sleep() { From 13ce9bfe3fe2361e1f5fddba0058c11b63bda841 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 5 Jul 2026 22:31:11 +0900 Subject: [PATCH 2/2] fix(review): give the publish gate the same evidence file as the model pool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause of reviews failing with "Selected successful OpenCode output did not include a valid control conclusion": for an APPROVE, valid_control() repairs the summary by filling the required review labels from the bounded evidence file resolved via OPENCODE_EVIDENCE_FILE / OPENCODE_APPROVAL_REPAIR_EVIDENCE_FILE. The model pool step sets OPENCODE_EVIDENCE_FILE, so the pool accepts a repaired APPROVE and records success. The publish step did NOT set it, so its re-run of the normalizer could not repair the same summary, rejected it (NO_CONCLUSION, exit 4), and failed the whole review — even though a model had produced a valid APPROVE. This surfaced once non-reasoning models (which emit a bare APPROVE summary) were ordered first. Expose the same OPENCODE_EVIDENCE_FILE in the publish step so both steps repair and validate identically. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01RTAMs4bpSZS77Xe3RQjv9P --- .github/workflows/opencode-review.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml index c8d6f96..ac48956 100644 --- a/.github/workflows/opencode-review.yml +++ b/.github/workflows/opencode-review.yml @@ -2391,6 +2391,13 @@ jobs: OPENCODE_MODEL_POOL_OUTCOME: ${{ steps.opencode_review_model_pool.outputs.review_status }} OPENCODE_MODEL_POOL_MODEL: ${{ steps.opencode_review_model_pool.outputs.review_model }} OPENCODE_MODEL_POOL_OUTPUT_FILE: ${{ runner.temp }}/opencode-review-model-pool.md + # Same bounded evidence file the model pool step exposed, so the + # publish gate's normalizer repairs an APPROVE summary (fills the + # required review labels from evidence) exactly as the pool did. + # Without it the pool accepts a repaired APPROVE but the publish gate + # re-rejects it (NO_CONCLUSION / exit 4), failing an otherwise valid + # review instead of publishing it. + OPENCODE_EVIDENCE_FILE: ${{ runner.temp }}/opencode-review-evidence.md # The publish gate re-runs source-backed validation against PR-head data. OPENCODE_SOURCE_WORKDIR: ${{ runner.temp }}/opencode-pr-head PR_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.inputs.pr_base_sha }}