Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 78 additions & 15 deletions .github/workflows/reusable-agents-verifier.yml
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,8 @@ jobs:
id: codex_model
if: steps.context.outputs.should_run == 'true' && inputs.mode != 'evaluate'
env:
DEFAULT_CODEX_MODEL: gpt-5.3-codex
DEFAULT_CODEX_MODEL: gpt-5.5
FALLBACK_CODEX_MODELS: gpt-5.4 gpt-5.3-codex
REQUESTED_MODEL: ${{ inputs.model }}
VERIFIER_MODE: ${{ inputs.mode }}
run: |
Expand All @@ -515,24 +516,32 @@ jobs:
reason="default"
fi

candidates="$model"
if [ "$reason" = "default" ]; then
candidates="$DEFAULT_CODEX_MODEL $FALLBACK_CODEX_MODELS"
fi

case "$model" in
gpt-5.2-codex)
echo "::warning::Codex model '$model' is not compatible with the configured ChatGPT auth; using '$DEFAULT_CODEX_MODEL'."
echo "::warning::Codex model '$model' is not compatible with the configured ChatGPT auth; trying '$DEFAULT_CODEX_MODEL' first."
model="$DEFAULT_CODEX_MODEL"
reason="fallback-unsupported-chatgpt-codex-model"
candidates="$DEFAULT_CODEX_MODEL $FALLBACK_CODEX_MODELS"
;;
esac

{
echo "model=$model"
echo "selection_reason=$reason"
echo "candidates=$candidates"
} >> "$GITHUB_OUTPUT"

{
echo "## Codex verifier model"
echo ""
echo "- Model: \`$model\`"
echo "- Selection reason: \`$reason\`"
echo "- Candidate order: \`$candidates\`"
} >> "$GITHUB_STEP_SUMMARY"

- name: Run verifier (checkbox mode)
Expand All @@ -541,21 +550,75 @@ jobs:
continue-on-error: true
env:
CODEX_HOME: ${{ runner.temp }}/.codex-verifier
CODEX_MODEL_CANDIDATES: ${{ steps.codex_model.outputs.candidates }}
CODEX_MODEL_SELECTION_REASON: ${{ steps.codex_model.outputs.selection_reason }}
run: |
set -euo pipefail
PROMPT_FILE="${{ steps.prepare.outputs.prompt_file }}"

set +e
codex exec \
--sandbox read-only \
--skip-git-repo-check \
--model "${{ steps.codex_model.outputs.model }}" \
--output-last-message codex-output.md \
"$(cat "$PROMPT_FILE")"
codex_exit_code=$?
set -euo pipefail
selected_model=""
selected_reason="${CODEX_MODEL_SELECTION_REASON:-input}"
codex_exit_code=1
attempt=0
IFS=' ' read -r -a codex_models <<< "${CODEX_MODEL_CANDIDATES:-}"
model_count="${#codex_models[@]}"

for codex_model in "${codex_models[@]}"; do
attempt=$((attempt + 1))
rm -f codex-output.md codex-stdout.log codex-stderr.log
echo "Running Codex verifier with model '${codex_model}' (${attempt}/${model_count})..."

set +e
codex exec \
--sandbox read-only \
--skip-git-repo-check \
--model "$codex_model" \
--output-last-message codex-output.md \
"$(cat "$PROMPT_FILE")" \
> codex-stdout.log 2> codex-stderr.log
codex_exit_code=$?
set -euo pipefail

if [ -s codex-stdout.log ]; then
echo "=== Codex stdout (${codex_model}) ==="
cat codex-stdout.log
fi
if [ -s codex-stderr.log ]; then
echo "=== Codex stderr (${codex_model}) ===" >&2
cat codex-stderr.log >&2
fi

selected_model="$codex_model"
if [ "$codex_exit_code" -eq 0 ]; then
if [ "$attempt" -gt 1 ]; then
selected_reason="runtime-fallback-model-unavailable"
fi
break
fi

if [ "$attempt" -lt "$model_count" ] &&
grep -Eiq "model.*(not supported|not available|unsupported|unavailable)|not compatible" \

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback detection grep pattern treats any occurrence of "not compatible" as a model-unavailable signal, even when the message isn't about the model (e.g., sandbox/config incompatibility). This can incorrectly trigger model fallback and mask the real failure. Consider tightening the regex to only match model-availability errors that clearly reference the model (e.g., patterns that include both "model" and "not compatible" / "unsupported"), or match Codex CLI’s specific error strings.

Suggested change
grep -Eiq "model.*(not supported|not available|unsupported|unavailable)|not compatible" \
grep -Eiq "(model.*(not supported|not available|unsupported|unavailable|not compatible))|((not supported|not available|unsupported|unavailable|not compatible).*model)" \

Copilot uses AI. Check for mistakes.
codex-stdout.log codex-stderr.log; then
echo "::warning::Codex model '${codex_model}' was unavailable; trying the next verifier model."
selected_reason="runtime-fallback-model-unavailable"
continue
fi

break
done

if [ -z "$selected_model" ]; then
selected_model="${{ steps.codex_model.outputs.model }}"
fi

{
echo "model=$selected_model"
echo "selection_reason=$selected_reason"
} >> "$GITHUB_OUTPUT"

echo "Codex verifier completed (exit code: ${codex_exit_code})"
echo "Codex verifier model: ${selected_model}"
echo "Codex verifier model selection reason: ${selected_reason}"
if [ -f codex-output.md ]; then
echo "=== Verifier Output ==="
cat codex-output.md
Expand Down Expand Up @@ -1256,8 +1319,8 @@ jobs:
CONTEXT_PATH: ${{ steps.context.outputs.context_path }}
ISSUE_NUMBER: ${{ steps.failure_issue.outputs.issue_number }}
SKIP_REASON: ${{ steps.context.outputs.skip_reason }}
CODEX_MODEL: ${{ steps.codex_model.outputs.model }}
CODEX_MODEL_SELECTION_REASON: ${{ steps.codex_model.outputs.selection_reason }}
CODEX_MODEL: ${{ steps.codex.outputs.model || steps.codex_model.outputs.model }}
CODEX_MODEL_SELECTION_REASON: ${{ steps.codex.outputs.selection_reason || steps.codex_model.outputs.selection_reason }}
VERIFIER_MODE: ${{ inputs.mode }}
run: |
set -euo pipefail
Expand Down Expand Up @@ -1327,8 +1390,8 @@ jobs:
PR_NUMBER: ${{ steps.context.outputs.pr_number }}
PR_URL: ${{ steps.context.outputs.pr_html_url }}
SKIP_REASON: ${{ steps.context.outputs.skip_reason }}
CODEX_MODEL: ${{ steps.codex_model.outputs.model }}
CODEX_MODEL_SELECTION_REASON: ${{ steps.codex_model.outputs.selection_reason }}
CODEX_MODEL: ${{ steps.codex.outputs.model || steps.codex_model.outputs.model }}
CODEX_MODEL_SELECTION_REASON: ${{ steps.codex.outputs.selection_reason || steps.codex_model.outputs.selection_reason }}
VERIFIER_MODE: ${{ inputs.mode }}
FINAL_VERDICT: >-
${{
Expand Down
24 changes: 18 additions & 6 deletions tests/workflows/test_verifier_terminal_disposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,33 @@ def test_reusable_verifier_uploads_terminal_disposition_artifact() -> None:
resolve_step.get("if")
== "steps.context.outputs.should_run == 'true' && inputs.mode != 'evaluate'"
)
assert resolve_step["env"]["DEFAULT_CODEX_MODEL"] == "gpt-5.3-codex"
assert resolve_step["env"]["DEFAULT_CODEX_MODEL"] == "gpt-5.5"
assert resolve_step["env"]["FALLBACK_CODEX_MODELS"] == "gpt-5.4 gpt-5.3-codex"
assert resolve_step["env"]["VERIFIER_MODE"] == "${{ inputs.mode }}"
assert "fallback-unsupported-chatgpt-codex-model" in resolve_step["run"]
assert "gpt-5.2-codex" in resolve_step["run"]
assert 'candidates="$DEFAULT_CODEX_MODEL $FALLBACK_CODEX_MODELS"' in resolve_step["run"]
assert "Candidate order" in resolve_step["run"]
assert '[ "${VERIFIER_MODE:-}" = "checkbox" ]' in resolve_step["run"]
assert '--model "${{ steps.codex_model.outputs.model }}"' in run_step["run"]
assert "CODEX_MODEL_CANDIDATES" in run_step["env"]
assert 'for codex_model in "${codex_models[@]}"; do' in run_step["run"]
assert '--model "$codex_model"' in run_step["run"]
assert "runtime-fallback-model-unavailable" in run_step["run"]
assert "steps.unified_verdict.outputs.verdict" in collect_step["env"]["VERDICT"]
assert collect_step["env"]["CODEX_MODEL"] == "${{ steps.codex_model.outputs.model }}"
assert collect_step["env"]["CODEX_MODEL"] == (
"${{ steps.codex.outputs.model || steps.codex_model.outputs.model }}"
)
assert collect_step["env"]["CODEX_MODEL_SELECTION_REASON"] == (
"${{ steps.codex_model.outputs.selection_reason }}"
"${{ steps.codex.outputs.selection_reason || "
"steps.codex_model.outputs.selection_reason }}"
)
assert write_step.get("if") == "always()"
assert write_step["env"]["CODEX_MODEL"] == "${{ steps.codex_model.outputs.model }}"
assert write_step["env"]["CODEX_MODEL"] == (
"${{ steps.codex.outputs.model || steps.codex_model.outputs.model }}"
)
assert write_step["env"]["CODEX_MODEL_SELECTION_REASON"] == (
"${{ steps.codex_model.outputs.selection_reason }}"
"${{ steps.codex.outputs.selection_reason || "
"steps.codex_model.outputs.selection_reason }}"
)
assert write_step["env"]["SOURCE_ISSUE_NUMBERS_JSON"] == (
"${{ steps.context.outputs.issue_numbers || '[]' }}"
Expand Down
Loading