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 }} 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() {