This is an automated email from the ASF dual-hosted git repository.

shuke987 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 537049c6f45 [fix](ci) Fall back when the review model is unavailable 
(#68535)
537049c6f45 is described below

commit 537049c6f454dda1994464c8ff0aa6f13d5f431b
Author: shuke <[email protected]>
AuthorDate: Mon Sep 28 11:13:25 2026 +0800

    [fix](ci) Fall back when the review model is unavailable (#68535)
    
    - Start automated reviews with `gpt-6-sol`, the current Sol model for
    Codex with ChatGPT sign-in.
    - If the initial model request is explicitly rejected before any review
    item runs, retry once with `gpt-5.6-sol`. Preserve the existing capacity
    recovery session and shared budget, and stop if both models are
    unavailable.
---
 .github/scripts/run_review_with_resume.py      |  59 +++++++++--
 .github/scripts/test_run_review_with_resume.py | 135 +++++++++++++++++++++++++
 .github/workflows/code-review-runner.yml       |   5 +-
 3 files changed, 186 insertions(+), 13 deletions(-)

diff --git a/.github/scripts/run_review_with_resume.py 
b/.github/scripts/run_review_with_resume.py
index c27a65c9fdf..eb6edb767fa 100755
--- a/.github/scripts/run_review_with_resume.py
+++ b/.github/scripts/run_review_with_resume.py
@@ -34,6 +34,7 @@ from pathlib import Path
 
 RETRY_DELAYS = (30, 60, 120)
 CAPACITY_MESSAGE = "Selected model is at capacity. Please try a different 
model."
+UNSUPPORTED_CHATGPT_MODEL = "model is not supported when using Codex with a 
ChatGPT account."
 PROCESS_EXIT_GRACE_SECONDS = 5
 
 
@@ -145,6 +146,19 @@ def attempt_result(events, status, stderr_path):
     )
 
 
+def can_fallback_model(events, message, model):
+    """Only a rejected first model request may start a new review session."""
+    return (
+        message is not None
+        and f"The '{model}' {UNSUPPORTED_CHATGPT_MODEL}" in message
+        and not any(
+            event.get("type") == "turn.completed"
+            or event.get("type", "").startswith("item.")
+            for event in events
+        )
+    )
+
+
 def session_id(events):
     ids = [
         event.get("thread_id")
@@ -339,6 +353,10 @@ def run_review(args, reaper=None):
     deadline = time.monotonic() + args.budget_seconds
     started_at = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
     thread_id = None
+    model = args.model
+    fallback_model = getattr(args, "fallback_model", None)
+    capacity_retry = 0
+    attempt_number = 0
 
     def remaining():
         seconds = deadline - time.monotonic()
@@ -362,10 +380,12 @@ def run_review(args, reaper=None):
         return 1
 
     try:
-        for attempt in range(len(RETRY_DELAYS) + 1):
-            events_path = attempts / f"{attempt + 1}.jsonl"
-            stderr_path = attempts / f"{attempt + 1}.stderr.log"
-            output_path = attempts / f"{attempt + 1}.final.txt"
+        while capacity_retry <= len(RETRY_DELAYS):
+            attempt_number += 1
+            events_path = attempts / f"{attempt_number}.jsonl"
+            stderr_path = attempts / f"{attempt_number}.stderr.log"
+            output_path = attempts / f"{attempt_number}.final.txt"
+            (context / "codex-review-model.txt").write_text(model + "\n")
             final_message.write_text("")
             command = [
                 "codex",
@@ -374,7 +394,7 @@ def run_review(args, reaper=None):
                 "--cd",
                 str(args.cwd),
                 "--model",
-                args.model,
+                model,
                 "--config",
                 f"model_reasoning_effort={args.effort}",
                 "--sandbox",
@@ -390,8 +410,8 @@ def run_review(args, reaper=None):
             else:
                 command += [goal_prompt]
             print(
-                f"Starting Codex review attempt {attempt + 1}/4 "
-                f"(session={thread_id or 'new'}, 
remaining={remaining():.0f}s)",
+                f"Starting Codex review attempt {attempt_number} "
+                f"(model={model}, session={thread_id or 'new'}, 
remaining={remaining():.0f}s)",
                 file=sys.stderr,
                 flush=True,
             )
@@ -417,13 +437,28 @@ def run_review(args, reaper=None):
                 )
             terminal, message = attempt_result(events, status, stderr_path)
             print(
-                f"Finished Codex review attempt {attempt + 1}/4 "
+                f"Finished Codex review attempt {attempt_number} "
                 f"(exit_status={status}, terminal_event={terminal}, 
events={len(events)})",
                 file=sys.stderr,
                 flush=True,
             )
+            if (
+                fallback_model
+                and fallback_model != model
+                and thread_id is None
+                and can_fallback_model(events, message, model)
+            ):
+                check_resume_target(args, started_at, remaining)
+                print(
+                    f"Model {model} is unavailable for this ChatGPT account; "
+                    f"retrying with {fallback_model} before review work began",
+                    file=sys.stderr,
+                    flush=True,
+                )
+                model = fallback_model
+                continue
             if message is not None and (
-                message != CAPACITY_MESSAGE or attempt == len(RETRY_DELAYS)
+                message != CAPACITY_MESSAGE or capacity_retry == 
len(RETRY_DELAYS)
             ):
                 return fail(message)
             current_id = session_id(events)
@@ -442,7 +477,7 @@ def run_review(args, reaper=None):
                 capture_output=True,
                 timeout=min(10, remaining()),
             )
-            delay = RETRY_DELAYS[attempt]
+            delay = RETRY_DELAYS[capacity_retry]
             if remaining() <= delay:
                 return fail("Insufficient shared review budget for capacity 
backoff")
             print(
@@ -456,7 +491,7 @@ def run_review(args, reaper=None):
                         {
                             "type": "review.capacity_retry",
                             "thread_id": thread_id,
-                            "next_attempt": attempt + 2,
+                            "next_attempt": attempt_number + 1,
                             "delay_seconds": delay,
                         }
                     )
@@ -464,6 +499,7 @@ def run_review(args, reaper=None):
                 )
             time.sleep(delay)
             check_resume_target(args, started_at, remaining)
+            capacity_retry += 1
     except KeyboardInterrupt:
         fail("Review cancelled; not resuming")
         return 130
@@ -484,6 +520,7 @@ def main():
     parser.add_argument("--head-sha", required=True)
     parser.add_argument("--base-sha", required=True)
     parser.add_argument("--model", required=True)
+    parser.add_argument("--fallback-model")
     parser.add_argument("--effort", required=True)
     # The workflow owns the total timeout and deducts setup/finalization time.
     parser.add_argument("--budget-seconds", type=int, required=True)
diff --git a/.github/scripts/test_run_review_with_resume.py 
b/.github/scripts/test_run_review_with_resume.py
index 54a69fb7916..01c49eaf4e8 100755
--- a/.github/scripts/test_run_review_with_resume.py
+++ b/.github/scripts/test_run_review_with_resume.py
@@ -71,6 +71,7 @@ class ResumeReviewTest(unittest.TestCase):
             head_sha="a" * 40,
             base_sha="b" * 40,
             model="gpt-5.6-sol",
+            fallback_model=None,
             effort="xhigh",
             budget_seconds=1000,
         )
@@ -154,6 +155,140 @@ class ResumeReviewTest(unittest.TestCase):
         self.target_check.assert_not_called()
         self.assertEqual([], self.sleeps)
 
+    def test_unsupported_model_falls_back_before_review_work(self):
+        self.args.model = "gpt-6-sol"
+        self.args.fallback_model = "gpt-5.6-sol"
+        rejection = (
+            "The 'gpt-6-sol' model is not supported when using Codex "
+            "with a ChatGPT account."
+        )
+        server_error = json.dumps(
+            {
+                "type": "error",
+                "status": 400,
+                "error": {"type": "invalid_request_error", "message": 
rejection},
+            }
+        )
+        self.assertEqual(
+            0,
+            self.execute(
+                [
+                    {
+                        "events": [
+                            thread_event(),
+                            {"type": "turn.started"},
+                            failed(server_error),
+                        ]
+                    },
+                    {"events": [thread_event(OTHER), completed()], "status": 
0},
+                ]
+            ),
+        )
+        self.assertEqual(
+            ["gpt-6-sol", "gpt-5.6-sol"],
+            [command[command.index("--model") + 1] for command in 
self.commands],
+        )
+        self.assertNotIn("resume", self.commands[1])
+        self.assertEqual(
+            "gpt-5.6-sol\n", (self.context / 
"codex-review-model.txt").read_text()
+        )
+        self.assertEqual([], self.sleeps)
+        self.target_check.assert_called_once()
+
+    def test_unsupported_model_does_not_restart_after_item(self):
+        self.args.model = "gpt-6-sol"
+        self.args.fallback_model = "gpt-5.6-sol"
+        rejection = (
+            "The 'gpt-6-sol' model is not supported when using Codex "
+            "with a ChatGPT account."
+        )
+        self.assertEqual(
+            1,
+            self.execute(
+                [
+                    {
+                        "events": [
+                            thread_event(),
+                            {"type": "item.started"},
+                            failed(rejection),
+                        ]
+                    }
+                ]
+            ),
+        )
+        self.assertEqual(1, len(self.commands))
+        self.target_check.assert_not_called()
+
+    def test_both_models_rejected_stops_after_one_fallback(self):
+        self.args.model = "gpt-6-sol"
+        self.args.fallback_model = "gpt-5.6-sol"
+
+        def rejection(model):
+            return (
+                f"The '{model}' model is not supported when using Codex "
+                "with a ChatGPT account."
+            )
+
+        self.assertEqual(
+            1,
+            self.execute(
+                [
+                    {"events": [thread_event(), 
failed(rejection("gpt-6-sol"))]},
+                    {"events": [thread_event(OTHER), 
failed(rejection("gpt-5.6-sol"))]},
+                ]
+            ),
+        )
+        self.assertEqual(2, len(self.commands))
+        self.assertEqual(rejection("gpt-5.6-sol"), self.last_error())
+
+    def test_capacity_resume_uses_the_fallback_model(self):
+        self.args.model = "gpt-6-sol"
+        self.args.fallback_model = "gpt-5.6-sol"
+        self.write_rollout(thread_id=OTHER)
+        rejection = (
+            "The 'gpt-6-sol' model is not supported when using Codex "
+            "with a ChatGPT account."
+        )
+        self.assertEqual(
+            0,
+            self.execute(
+                [
+                    {"events": [thread_event(), failed(rejection)]},
+                    {"events": [thread_event(OTHER), failed()]},
+                    {"events": [thread_event(OTHER), completed()], "status": 
0},
+                ]
+            ),
+        )
+        self.assertEqual([30], self.sleeps)
+        self.assertEqual(
+            ["gpt-6-sol", "gpt-5.6-sol", "gpt-5.6-sol"],
+            [command[command.index("--model") + 1] for command in 
self.commands],
+        )
+        self.assertEqual(["resume", OTHER], self.commands[2][-3:-1])
+        self.assertEqual(
+            "gpt-5.6-sol\n", (self.context / 
"codex-review-model.txt").read_text()
+        )
+
+    def test_unsupported_model_during_resume_never_starts_another_review(self):
+        self.args.model = "gpt-6-sol"
+        self.args.fallback_model = "gpt-5.6-sol"
+        rejection = (
+            "The 'gpt-6-sol' model is not supported when using Codex "
+            "with a ChatGPT account."
+        )
+        self.assertEqual(
+            1,
+            self.execute(
+                [
+                    {"events": [thread_event(), failed()]},
+                    {"events": [thread_event(), failed(rejection)]},
+                ]
+            ),
+        )
+        self.assertEqual(2, len(self.commands))
+        self.assertEqual([30], self.sleeps)
+        self.assertEqual(rejection, self.last_error())
+
     def 
test_capacity_resumes_exact_session_with_same_settings_and_ledger(self):
         before = self.ledger.read_text()
         self.assertEqual(
diff --git a/.github/workflows/code-review-runner.yml 
b/.github/workflows/code-review-runner.yml
index dcb97cf206d..75461551e89 100644
--- a/.github/workflows/code-review-runner.yml
+++ b/.github/workflows/code-review-runner.yml
@@ -728,7 +728,8 @@ jobs:
             --pr-number "$PR_NUMBER" \
             --head-sha "$HEAD_SHA" \
             --base-sha "$BASE_SHA" \
-            --model "gpt-5.6-sol" \
+            --model "gpt-6-sol" \
+            --fallback-model "gpt-5.6-sol" \
             --effort xhigh \
             --budget-seconds "$budget_seconds"
           status=$?
@@ -1010,7 +1011,7 @@ jobs:
             --pr-number "$PR_NUMBER" \
             --head-sha "$HEAD_SHA" \
             --base-sha "$BASE_SHA" \
-            --model "gpt-5.6-sol" \
+            --model "$(cat "$REVIEW_CONTEXT_DIR/codex-review-model.txt")" \
             --reasoning-effort "xhigh" \
             --environment "github-actions" \
             --max-payload-bytes 4000000 \


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to