diff --git a/.github/workflows/qwen-triage.yml b/.github/workflows/qwen-triage.yml index 9b4482dcee4..44160df4b37 100644 --- a/.github/workflows/qwen-triage.yml +++ b/.github/workflows/qwen-triage.yml @@ -942,6 +942,30 @@ jobs: fi exit 1 fi + # A non-empty response is not necessarily a triage: on an API error + # the stream-json adapter appends the formatted error LAST + # (BaseJsonOutputAdapter appendText), optionally followed by + # rate-limit guidance. Run 33070765162 (triage for #10285) answered + # with a bare 268-char "[API Error: Connection error. ...]" that + # this step passed as success, so nothing retried or alerted. Strip + # the known suffixes, right-trim, then fail on the trailing shape -- + # a real summary that merely quotes an API error mid-prose keeps + # writing afterwards and stays green. Same pattern as + # qwen-code-pr-review.yml. + BODY="${RESPONSE}" + for S in \ + 'Possible quota limitations in place or slow response times detected. Please wait and try again later.' \ + 'Please wait and try again later. To increase your limits, request a quota increase through AI Studio, or switch to another /auth method' \ + 'Please wait and try again later. To increase your limits, request a quota increase through Vertex, or switch to another /auth method'; do + BODY="${BODY%"$S"}" + done + BODY="${BODY%"${BODY##*[![:space:]]}"}" + case "${BODY}" in + *"[API Error: "*"]") + echo "::error title=Triage aborted by an API error::The model API returned an error instead of a triage, so no triage was posted. This is infrastructure, not this issue -- re-run the failed job." + exit 1 + ;; + esac echo "Triage response received (${#RESPONSE} chars)." - name: 'Notify silent triage re-run' diff --git a/scripts/tests/qwen-triage-workflow.test.js b/scripts/tests/qwen-triage-workflow.test.js index 5671b20339c..39faf010ad9 100644 --- a/scripts/tests/qwen-triage-workflow.test.js +++ b/scripts/tests/qwen-triage-workflow.test.js @@ -372,6 +372,63 @@ describe('qwen-triage tmux workflow', () => { expect(run({ RESPONSE: 'null' }).status).not.toBe(0); }); + it('fails an API-error response instead of passing it as a triage (#10314)', () => { + const checkStep = step('Check triage response'); + const body = checkStep.match(/run: \|-\n([\s\S]*)$/)?.[1]; + expect(body).toBeTruthy(); + const script = body.replace(/^ {10}/gm, ''); + const run = (env) => { + const proc = spawnSync('bash', ['-c', script], { + env: { + ...process.env, + RESPONSE: '', + TRIAGE_OUTCOME: 'success', + ...env, + }, + encoding: 'utf8', + }); + return { status: proc.status, out: `${proc.stdout}${proc.stderr}` }; + }; + + // The verbatim 268-char response of run 33070765162 (triage for #10285): + // a bare model-layer connection error that the old check classified as a + // successful triage, so nothing retried or alerted. + const apiError = + '[API Error: Connection error. (cause: connect ETIMEDOUT 47.94.20.201:443; connect ENETUNREACH 2408:400a:3e:effd:6ac1:ae6e:cde9:4efe:443 - Local (:::0); connect ETIMEDOUT 101.201.58.201:443; connect ENETUNREACH 2408:400a:3e:effb:c146:fb04:1e3d:5cc1:443 - Local (:::0))]'; + const bare = run({ RESPONSE: apiError }); + expect(bare.status).not.toBe(0); + expect(bare.out).toContain('API error'); + + // The stream-json adapter appends the formatted error LAST + // (BaseJsonOutputAdapter appendText), optionally followed by rate-limit + // guidance: an error after partial output and a quota error ending in + // the guidance suffix must fail too. Same shapes as + // qwen-code-pr-review.yml's classifier. + expect( + run({ RESPONSE: `Partial triage notes\n${apiError}` }).status, + ).not.toBe(0); + expect( + run({ + RESPONSE: + '[API Error: Quota exceeded.] Please wait and try again later. To increase your limits, request a quota increase through AI Studio, or switch to another /auth method', + }).status, + ).not.toBe(0); + + // A real summary still passes: one that merely QUOTES an API error + // mid-prose keeps writing afterwards (parity with the pr-review + // workflow's success_mentions_api_error case), and the normal + // empty/'null' behavior is untouched. + expect( + run({ + RESPONSE: + 'This issue reports "[API Error: Connection error.]" which points at the model endpoint; needs the endpoint config.', + }).status, + ).toBe(0); + expect(run({ RESPONSE: 'triaged' }).status).toBe(0); + expect(run({ RESPONSE: '' }).status).not.toBe(0); + expect(run({ RESPONSE: 'null' }).status).not.toBe(0); + }); + it('notifies the author when a manual triage re-run posts no review', () => { const notifyStep = step('Notify silent triage re-run');