From 36fa1ed4c91b623348df63cbb04edb25f44d7a86 Mon Sep 17 00:00:00 2001 From: wenshao Date: Tue, 4 Aug 2026 02:50:24 +0800 Subject: [PATCH 1/7] test(ci): align resolve-workflow timeout expectations with externalized variables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #8460 moved the review timeout numbers into repository variables (QWEN_REVIEW_JOB_TIMEOUT_MINUTES / QWEN_REVIEW_MAX_TIMEOUT_MINUTES) but left qwen-resolve-workflow.test.js expecting the old hardcoded 300/240 values — main's Test lane has been red since. The five expectations now pin the variable-driven forms the workflow actually carries. --- scripts/tests/qwen-resolve-workflow.test.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/scripts/tests/qwen-resolve-workflow.test.js b/scripts/tests/qwen-resolve-workflow.test.js index 904f3fa66a1..76393533711 100644 --- a/scripts/tests/qwen-resolve-workflow.test.js +++ b/scripts/tests/qwen-resolve-workflow.test.js @@ -346,7 +346,9 @@ describe('qwen resolve workflow', () => { const contextStep = step(reviewJob, 'Resolve PR context'); const runStep = step(reviewJob, 'Run review'); - expect(reviewJob).toContain('timeout-minutes: 300'); + expect(reviewJob).toContain( + "timeout-minutes: '${{ fromJSON(vars.QWEN_REVIEW_JOB_TIMEOUT_MINUTES) }}'", + ); expect(contextStep).toContain('DEFAULT_TIMEOUT_MINUTES=180'); expect(contextStep).toContain('case "$token" in'); expect(contextStep).toContain('--timeout=*)'); @@ -354,7 +356,7 @@ describe('qwen resolve workflow', () => { expect(contextStep).toContain('timeout=*)'); expect(contextStep).toContain('TIMEOUT_MINUTES="${token#timeout=}"'); expect(runStep).toContain('if [ "${#TIMEOUT_MINUTES}" -gt 3 ]; then'); - expect(runStep).toContain('timeout_minutes must not exceed 240 minutes'); + expect(runStep).toContain('timeout_minutes must not exceed ${MAX_TIMEOUT_MINUTES} minutes'); expect(runStep).toContain('QWEN_TIMEOUT="$EFFECTIVE_TIMEOUT_MINUTES"'); expect(runStep).not.toContain('QWEN_TIMEOUT=$((TIMEOUT_MINUTES - 5))'); }); @@ -381,7 +383,9 @@ describe('qwen resolve workflow', () => { expect(runStep).toContain('--json additions,deletions'); expect(runStep).toContain('if [ "$PR_SIZE_LINES" -le 300 ]; then'); expect(runStep).toContain('EFFECTIVE_TIMEOUT_MINUTES=180'); - expect(runStep).toContain('EFFECTIVE_TIMEOUT_MINUTES=240'); + expect(runStep).toContain( + 'EFFECTIVE_TIMEOUT_MINUTES="${{ vars.QWEN_REVIEW_MAX_TIMEOUT_MINUTES }}"', + ); expect(runStep).not.toContain('EFFECTIVE_TIMEOUT_MINUTES=210'); expect(runStep).toContain( 'echo "effective_timeout_minutes=$EFFECTIVE_TIMEOUT_MINUTES"', @@ -403,9 +407,11 @@ describe('qwen resolve workflow', () => { expect(fallbackStep).toContain( "TIMEOUT_MINUTES: '${{ steps.review.outputs.effective_timeout_minutes || steps.context.outputs.timeout_minutes }}'", ); - expect(fallbackStep).toContain('@qwen-code /review --timeout=240'); expect(fallbackStep).toContain( - 'This run already used the maximum 240 minute timeout.', + '@qwen-code /review --timeout=${MAX_TIMEOUT_MINUTES}', + ); + expect(fallbackStep).toContain( + 'This run already used the maximum ${MAX_TIMEOUT_MINUTES} minute timeout.', ); expect(fallbackStep).toContain('**Qwen Code review timed out.**'); expect(fallbackStep).not.toContain( From 99f5fb036533ecb5c7a836b617ed46034217a76a Mon Sep 17 00:00:00 2001 From: Qwen Autofix Date: Mon, 3 Aug 2026 21:42:11 +0000 Subject: [PATCH 2/7] test(ci): pin resolve-workflow timeout enforcement, tiering, and fallback machinery --- scripts/tests/qwen-resolve-workflow.test.js | 40 ++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/scripts/tests/qwen-resolve-workflow.test.js b/scripts/tests/qwen-resolve-workflow.test.js index 76393533711..88b83b50f17 100644 --- a/scripts/tests/qwen-resolve-workflow.test.js +++ b/scripts/tests/qwen-resolve-workflow.test.js @@ -356,7 +356,15 @@ describe('qwen resolve workflow', () => { expect(contextStep).toContain('timeout=*)'); expect(contextStep).toContain('TIMEOUT_MINUTES="${token#timeout=}"'); expect(runStep).toContain('if [ "${#TIMEOUT_MINUTES}" -gt 3 ]; then'); - expect(runStep).toContain('timeout_minutes must not exceed ${MAX_TIMEOUT_MINUTES} minutes'); + expect(runStep).toContain( + 'MAX_TIMEOUT_MINUTES="${{ vars.QWEN_REVIEW_MAX_TIMEOUT_MINUTES }}"', + ); + expect(runStep).toContain( + 'if [ "$TIMEOUT_MINUTES" -gt "$MAX_TIMEOUT_MINUTES" ]; then', + ); + expect(runStep).toContain( + 'fail "timeout_minutes must not exceed ${MAX_TIMEOUT_MINUTES} minutes"', + ); expect(runStep).toContain('QWEN_TIMEOUT="$EFFECTIVE_TIMEOUT_MINUTES"'); expect(runStep).not.toContain('QWEN_TIMEOUT=$((TIMEOUT_MINUTES - 5))'); }); @@ -374,18 +382,26 @@ describe('qwen resolve workflow', () => { ); // Auto-tiering only applies without an explicit --timeout, keys off - // additions + deletions, and never exceeds the 240 cap: small PRs keep 180, - // anything larger gets the full 240. + // additions + deletions, and never exceeds the QWEN_REVIEW_MAX_TIMEOUT_MINUTES + // cap: small PRs keep 180, anything larger gets the full cap. expect(runStep).toContain('EFFECTIVE_TIMEOUT_MINUTES="$TIMEOUT_MINUTES"'); expect(runStep).toContain( 'if [ "${TIMEOUT_EXPLICIT:-false}" != "true" ]; then', ); expect(runStep).toContain('--json additions,deletions'); + expect(runStep).toContain('if [ -n "$PR_SIZE_LINES" ]; then'); expect(runStep).toContain('if [ "$PR_SIZE_LINES" -le 300 ]; then'); expect(runStep).toContain('EFFECTIVE_TIMEOUT_MINUTES=180'); expect(runStep).toContain( 'EFFECTIVE_TIMEOUT_MINUTES="${{ vars.QWEN_REVIEW_MAX_TIMEOUT_MINUTES }}"', ); + // Unordered containment would survive swapping the two assignments between + // the branches, which silently inverts the tiers. + expect(runStep.indexOf('EFFECTIVE_TIMEOUT_MINUTES=180')).toBeLessThan( + runStep.indexOf( + 'EFFECTIVE_TIMEOUT_MINUTES="${{ vars.QWEN_REVIEW_MAX_TIMEOUT_MINUTES }}"', + ), + ); expect(runStep).not.toContain('EFFECTIVE_TIMEOUT_MINUTES=210'); expect(runStep).toContain( 'echo "effective_timeout_minutes=$EFFECTIVE_TIMEOUT_MINUTES"', @@ -402,17 +418,33 @@ describe('qwen resolve workflow', () => { 'REASON="Qwen review timed out after ${attempt_timeout} seconds (of the ${QWEN_TIMEOUT}-minute budget)."', ); expect(runStep).toContain('[ "$qwen_status" -eq 137 ]'); - expect(fallbackStep).toContain('FAILURE_KIND:'); + expect(fallbackStep).toContain('failure() &&'); + expect(fallbackStep).toContain( + 'FAILURE_KIND: "${{ steps.review.outputs.failure_kind || \'\' }}"', + ); expect(fallbackStep).toContain('TIMEOUT_MINUTES:'); expect(fallbackStep).toContain( "TIMEOUT_MINUTES: '${{ steps.review.outputs.effective_timeout_minutes || steps.context.outputs.timeout_minutes }}'", ); expect(fallbackStep).toContain( + 'MAX_TIMEOUT_MINUTES="${{ vars.QWEN_REVIEW_MAX_TIMEOUT_MINUTES }}"', + ); + expect(fallbackStep).toContain('if [ "$FAILURE_KIND" = "timeout" ]; then'); + // Slice the below-max arm so a transposition of the two bodies fails: + // unordered containment keeps both texts present in the wrong arms. + const belowMaxArm = fallbackStep.slice( + fallbackStep.indexOf( + 'if [ "$TIMEOUT_MINUTES" -lt "$MAX_TIMEOUT_MINUTES" ]; then', + ), + fallbackStep.indexOf('else'), + ); + expect(belowMaxArm).toContain( '@qwen-code /review --timeout=${MAX_TIMEOUT_MINUTES}', ); expect(fallbackStep).toContain( 'This run already used the maximum ${MAX_TIMEOUT_MINUTES} minute timeout.', ); + expect(fallbackStep).toContain('gh pr comment "$PR_NUMBER"'); expect(fallbackStep).toContain('**Qwen Code review timed out.**'); expect(fallbackStep).not.toContain( '_Qwen Code review did not complete successfully:', From 13517077775bd733c074f62096f6e3fc6ffccba0 Mon Sep 17 00:00:00 2001 From: wenshao Date: Tue, 4 Aug 2026 08:43:30 +0800 Subject: [PATCH 3/7] test(ci): pin the timeout-advice branch CONDITION, not just both bodies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With both branch bodies asserted as substrings of the same step, any comparison flip (-ge/-gt/-le) keeps both strings present and ships the wrong recovery advice on every timeout — a below-max run told it already used the maximum, an at-cap run told to retry with a timeout that cannot work. Raised in #8388's review round (R4-21). --- scripts/tests/qwen-resolve-workflow.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/tests/qwen-resolve-workflow.test.js b/scripts/tests/qwen-resolve-workflow.test.js index 88b83b50f17..22f46319339 100644 --- a/scripts/tests/qwen-resolve-workflow.test.js +++ b/scripts/tests/qwen-resolve-workflow.test.js @@ -441,6 +441,12 @@ describe('qwen resolve workflow', () => { expect(belowMaxArm).toContain( '@qwen-code /review --timeout=${MAX_TIMEOUT_MINUTES}', ); + // The branch CONDITION, not just both branch bodies: with both bodies + // pinned as substrings, any comparison flip (-ge/-gt/-le) keeps both + // strings present and ships the wrong recovery advice on every timeout. + expect(fallbackStep).toContain( + 'if [ "$TIMEOUT_MINUTES" -lt "$MAX_TIMEOUT_MINUTES" ]; then', + ); expect(fallbackStep).toContain( 'This run already used the maximum ${MAX_TIMEOUT_MINUTES} minute timeout.', ); From 9933b2fe6a2c135e9005c2069a05084797d9f9e4 Mon Sep 17 00:00:00 2001 From: Qwen Autofix Date: Tue, 4 Aug 2026 00:46:23 +0000 Subject: [PATCH 4/7] test(ci): anchor resolve-workflow arm slices at their if starts Address review feedback on #8485: search for each sliced arm's else from the arm's own if start so an unrelated earlier if/else in the step cannot invert the slice (or vacuously satisfy .not.toContain on an empty slice). Also slice the small-PR tier arm instead of comparing first-occurrence indexes, unifying the technique and proving the assignments sit in opposite arms. --- scripts/tests/qwen-resolve-workflow.test.js | 39 ++++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/scripts/tests/qwen-resolve-workflow.test.js b/scripts/tests/qwen-resolve-workflow.test.js index 88b83b50f17..8b8e22767fe 100644 --- a/scripts/tests/qwen-resolve-workflow.test.js +++ b/scripts/tests/qwen-resolve-workflow.test.js @@ -395,13 +395,18 @@ describe('qwen resolve workflow', () => { expect(runStep).toContain( 'EFFECTIVE_TIMEOUT_MINUTES="${{ vars.QWEN_REVIEW_MAX_TIMEOUT_MINUTES }}"', ); - // Unordered containment would survive swapping the two assignments between - // the branches, which silently inverts the tiers. - expect(runStep.indexOf('EFFECTIVE_TIMEOUT_MINUTES=180')).toBeLessThan( - runStep.indexOf( - 'EFFECTIVE_TIMEOUT_MINUTES="${{ vars.QWEN_REVIEW_MAX_TIMEOUT_MINUTES }}"', - ), - ); + // Slice the small-PR arm so a swap of the two assignments between the + // branches fails: unordered containment keeps both texts present. + const smallPrStart = runStep.indexOf( + 'if [ "$PR_SIZE_LINES" -le 300 ]; then', + ); + expect(smallPrStart).toBeGreaterThan(-1); + const smallPrArm = runStep.slice( + smallPrStart, + runStep.indexOf('else', smallPrStart), + ); + expect(smallPrArm).toContain('EFFECTIVE_TIMEOUT_MINUTES=180'); + expect(smallPrArm).not.toContain('vars.QWEN_REVIEW_MAX_TIMEOUT_MINUTES'); expect(runStep).not.toContain('EFFECTIVE_TIMEOUT_MINUTES=210'); expect(runStep).toContain( 'echo "effective_timeout_minutes=$EFFECTIVE_TIMEOUT_MINUTES"', @@ -432,11 +437,15 @@ describe('qwen resolve workflow', () => { expect(fallbackStep).toContain('if [ "$FAILURE_KIND" = "timeout" ]; then'); // Slice the below-max arm so a transposition of the two bodies fails: // unordered containment keeps both texts present in the wrong arms. + // Search for the else from the arm start so an unrelated earlier if/else + // in this step cannot invert the slice. + const belowMaxStart = fallbackStep.indexOf( + 'if [ "$TIMEOUT_MINUTES" -lt "$MAX_TIMEOUT_MINUTES" ]; then', + ); + expect(belowMaxStart).toBeGreaterThan(-1); const belowMaxArm = fallbackStep.slice( - fallbackStep.indexOf( - 'if [ "$TIMEOUT_MINUTES" -lt "$MAX_TIMEOUT_MINUTES" ]; then', - ), - fallbackStep.indexOf('else'), + belowMaxStart, + fallbackStep.indexOf('else', belowMaxStart), ); expect(belowMaxArm).toContain( '@qwen-code /review --timeout=${MAX_TIMEOUT_MINUTES}', @@ -607,9 +616,13 @@ describe('qwen resolve workflow', () => { expect(authorizeStep).toMatch( /if \[ "\$PR_ACTION" = "review_requested" \]; then\s+principal="\$SENDER"/, ); + const reviewRequestedStart = authorizeStep.indexOf( + 'if [ "$PR_ACTION" = "review_requested" ]; then', + ); + expect(reviewRequestedStart).toBeGreaterThan(-1); const reviewRequestedBranch = authorizeStep.slice( - authorizeStep.indexOf('if [ "$PR_ACTION" = "review_requested" ]; then'), - authorizeStep.indexOf('else'), + reviewRequestedStart, + authorizeStep.indexOf('else', reviewRequestedStart), ); expect(reviewRequestedBranch).toContain('principal="$SENDER"'); expect(reviewRequestedBranch).not.toContain( From 28b37b26d91d6398765f61b9b4469d23b93f4d57 Mon Sep 17 00:00:00 2001 From: Qwen Autofix Date: Tue, 4 Aug 2026 05:06:06 +0000 Subject: [PATCH 5/7] test(ci): keep the max-timeout text out of the below-max arm slice --- scripts/tests/qwen-resolve-workflow.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/tests/qwen-resolve-workflow.test.js b/scripts/tests/qwen-resolve-workflow.test.js index accb519008b..c793a2fab96 100644 --- a/scripts/tests/qwen-resolve-workflow.test.js +++ b/scripts/tests/qwen-resolve-workflow.test.js @@ -450,6 +450,7 @@ describe('qwen resolve workflow', () => { expect(belowMaxArm).toContain( '@qwen-code /review --timeout=${MAX_TIMEOUT_MINUTES}', ); + expect(belowMaxArm).not.toContain('This run already used the maximum'); // The branch CONDITION, not just both branch bodies: with both bodies // pinned as substrings, any comparison flip (-ge/-gt/-le) keeps both // strings present and ships the wrong recovery advice on every timeout. From 197264d8bd1d585888d0e673e1872655d086f371 Mon Sep 17 00:00:00 2001 From: Qwen Autofix Date: Tue, 4 Aug 2026 10:31:25 +0000 Subject: [PATCH 6/7] test(ci): pin fallback arm bodies, comment wiring, and tiering order --- scripts/tests/qwen-resolve-workflow.test.js | 69 +++++++++++++++++++-- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/scripts/tests/qwen-resolve-workflow.test.js b/scripts/tests/qwen-resolve-workflow.test.js index c793a2fab96..d0c0fc96e21 100644 --- a/scripts/tests/qwen-resolve-workflow.test.js +++ b/scripts/tests/qwen-resolve-workflow.test.js @@ -391,6 +391,16 @@ describe('qwen resolve workflow', () => { expect(runStep).toContain('--json additions,deletions'); expect(runStep).toContain('if [ -n "$PR_SIZE_LINES" ]; then'); expect(runStep).toContain('if [ "$PR_SIZE_LINES" -le 300 ]; then'); + // The size guard must WRAP the comparison it protects: swapping the two + // ifs keeps both texts present while an empty PR_SIZE_LINES (a failed + // size lookup) hits the bare integer test and silently gets the cap. + const sizeGuardStart = runStep.indexOf('if [ -n "$PR_SIZE_LINES" ]; then'); + expect(sizeGuardStart).toBeGreaterThan(-1); + const sizeGuardArm = runStep.slice( + sizeGuardStart, + runStep.indexOf('else', sizeGuardStart), + ); + expect(sizeGuardArm).toContain('if [ "$PR_SIZE_LINES" -le 300 ]; then'); expect(runStep).toContain('EFFECTIVE_TIMEOUT_MINUTES=180'); expect(runStep).toContain( 'EFFECTIVE_TIMEOUT_MINUTES="${{ vars.QWEN_REVIEW_MAX_TIMEOUT_MINUTES }}"', @@ -411,6 +421,25 @@ describe('qwen resolve workflow', () => { expect(runStep).toContain( 'echo "effective_timeout_minutes=$EFFECTIVE_TIMEOUT_MINUTES"', ); + // Every check above is order-independent containment: pin that both + // tiering writes precede both consumers, or a block moved below its + // consumer keeps the suite green while non-small PRs run on the + // pre-tier budget (the exact incident this feature exists for). + const tierInit = runStep.indexOf( + 'EFFECTIVE_TIMEOUT_MINUTES="$TIMEOUT_MINUTES"', + ); + const tierStart = runStep.indexOf( + 'if [ "${TIMEOUT_EXPLICIT:-false}" != "true" ]; then', + ); + for (const consumer of [ + runStep.indexOf('QWEN_TIMEOUT="$EFFECTIVE_TIMEOUT_MINUTES"'), + runStep.indexOf( + 'echo "effective_timeout_minutes=$EFFECTIVE_TIMEOUT_MINUTES"', + ), + ]) { + expect(tierInit).toBeLessThan(consumer); + expect(tierStart).toBeLessThan(consumer); + } }); it('tells maintainers how to retry timed-out reviews with more time', () => { @@ -451,17 +480,49 @@ describe('qwen resolve workflow', () => { '@qwen-code /review --timeout=${MAX_TIMEOUT_MINUTES}', ); expect(belowMaxArm).not.toContain('This run already used the maximum'); + // Symmetric slice for the at-max arm: it is an adjacent body= assignment + // in the same if-chain as the quota branch, the exact transposition class + // the belowMaxArm slice catches. + const atMaxStart = fallbackStep.indexOf('else', belowMaxStart); + expect(atMaxStart).toBeGreaterThan(-1); + const atMaxArm = fallbackStep.slice( + atMaxStart, + fallbackStep.indexOf('fi', atMaxStart), + ); + expect(atMaxArm).toContain( + 'This run already used the maximum ${MAX_TIMEOUT_MINUTES} minute timeout.', + ); + expect(atMaxArm).not.toContain('/review --timeout='); + // The quota branch carries its own recovery advice; pin it to its arm. + const quotaStart = fallbackStep.indexOf( + 'elif [ "$FAILURE_KIND" = "quota" ]; then', + ); + expect(quotaStart).toBeGreaterThan(-1); + const quotaArm = fallbackStep.slice( + quotaStart, + fallbackStep.indexOf('else', quotaStart), + ); + expect(quotaArm).toContain( + '**Qwen Code review paused — model quota exhausted.**', + ); // The branch CONDITION, not just both branch bodies: with both bodies // pinned as substrings, any comparison flip (-ge/-gt/-le) keeps both // strings present and ships the wrong recovery advice on every timeout. expect(fallbackStep).toContain( 'if [ "$TIMEOUT_MINUTES" -lt "$MAX_TIMEOUT_MINUTES" ]; then', ); - expect(fallbackStep).toContain( - 'This run already used the maximum ${MAX_TIMEOUT_MINUTES} minute timeout.', - ); - expect(fallbackStep).toContain('gh pr comment "$PR_NUMBER"'); expect(fallbackStep).toContain('**Qwen Code review timed out.**'); + // The comment must come AFTER all three arms: containment holds wherever + // the line sits, so a move into one arm would silently drop the others. + const genericBodyStart = fallbackStep.indexOf( + '**Qwen Code review did not complete successfully.**', + ); + expect(genericBodyStart).toBeGreaterThan(-1); + const commentStart = fallbackStep.indexOf('gh pr comment "$PR_NUMBER"'); + expect(commentStart).toBeGreaterThan(genericBodyStart); + // The wiring that delivers every body pinned above: pointing the command + // at a different variable posts text none of these assertions protect. + expect(fallbackStep).toContain('--body "$body"'); expect(fallbackStep).not.toContain( '_Qwen Code review did not complete successfully:', ); From 3adde1acbfb0065a96593368931ef756689ff6e2 Mon Sep 17 00:00:00 2001 From: verify Date: Tue, 4 Aug 2026 21:31:42 +0800 Subject: [PATCH 7/7] review: line-anchor the at-max arm's closing fi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A bare indexOf('fi') stops at the first word CONTAINING the letters — 'specified', 'notification' — so a harmless wording change inside the at-max arm would silently truncate the slice and hand the arm's not-toContain a vacuous pass. The end anchor is now the line-anchored /\n\s*fi\b/, with its own found-assertion. --- scripts/tests/qwen-resolve-workflow.test.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/tests/qwen-resolve-workflow.test.js b/scripts/tests/qwen-resolve-workflow.test.js index d0c0fc96e21..d3e0bc6c5c2 100644 --- a/scripts/tests/qwen-resolve-workflow.test.js +++ b/scripts/tests/qwen-resolve-workflow.test.js @@ -485,10 +485,12 @@ describe('qwen resolve workflow', () => { // the belowMaxArm slice catches. const atMaxStart = fallbackStep.indexOf('else', belowMaxStart); expect(atMaxStart).toBeGreaterThan(-1); - const atMaxArm = fallbackStep.slice( - atMaxStart, - fallbackStep.indexOf('fi', atMaxStart), - ); + // Line-anchored end: a bare indexOf('fi') stops at the first word + // CONTAINING "fi" ("specified", "notification"), silently truncating the + // arm and giving the not-toContain below a vacuous pass. + const atMaxEnd = fallbackStep.slice(atMaxStart).search(/\n\s*fi\b/); + expect(atMaxEnd).toBeGreaterThan(-1); + const atMaxArm = fallbackStep.slice(atMaxStart, atMaxStart + atMaxEnd); expect(atMaxArm).toContain( 'This run already used the maximum ${MAX_TIMEOUT_MINUTES} minute timeout.', );