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
21 changes: 12 additions & 9 deletions .github/scripts/__tests__/keepalive-loop.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ test('evaluateKeepaliveLoop treats cancelled gate as transient wait', async () =
assert.equal(result.reason, 'gate-cancelled');
});

test('evaluateKeepaliveLoop detects rate limit cancelled gate', async () => {
test('evaluateKeepaliveLoop bypasses rate limit cancelled gate', async () => {
const pr = {
number: 509,
head: { ref: 'feature/cancelled-rate', sha: 'sha-cancelled-rate' },
Expand All @@ -440,11 +440,12 @@ test('evaluateKeepaliveLoop detects rate limit cancelled gate', async () => {
context: buildContext(pr.number),
core: buildCore(),
});
assert.equal(result.action, 'defer');
assert.equal(result.reason, 'gate-cancelled-rate-limit');
// Rate limits are infrastructure noise - work should proceed
assert.equal(result.action, 'run');
assert.equal(result.reason, 'bypass-rate-limit-gate');
});

test('evaluateKeepaliveLoop detects rate limit cancelled gate from logs', async () => {
test('evaluateKeepaliveLoop bypasses rate limit cancelled gate from logs', async () => {
const pr = {
number: 510,
head: { ref: 'feature/cancelled-rate-logs', sha: 'sha-cancelled-rate-logs' },
Expand All @@ -464,8 +465,9 @@ test('evaluateKeepaliveLoop detects rate limit cancelled gate from logs', async
context: buildContext(pr.number),
core: buildCore(),
});
assert.equal(result.action, 'defer');
assert.equal(result.reason, 'gate-cancelled-rate-limit');
// Rate limits are infrastructure noise - work should proceed
assert.equal(result.action, 'run');
assert.equal(result.reason, 'bypass-rate-limit-gate');
});

test('evaluateKeepaliveLoop force_retry bypasses cancelled gate', async () => {
Expand All @@ -490,7 +492,7 @@ test('evaluateKeepaliveLoop force_retry bypasses cancelled gate', async () => {
assert.equal(result.forceRetry, true);
});

test('evaluateKeepaliveLoop force_retry bypasses rate limit deferred gate', async () => {
test('evaluateKeepaliveLoop rate limit bypass takes precedence over force_retry', async () => {
const pr = {
number: 512,
head: { ref: 'feature/force-retry-rate', sha: 'sha-force-retry-rate' },
Expand All @@ -511,9 +513,10 @@ test('evaluateKeepaliveLoop force_retry bypasses rate limit deferred gate', asyn
core: buildCore(),
forceRetry: true,
});
// Even rate-limited cancellations should be bypassed with forceRetry
// Rate limit bypass is automatic infrastructure handling - takes precedence
// forceRetry is still honored for non-rate-limit cases
assert.equal(result.action, 'run');
assert.equal(result.reason, 'force-retry-cancelled');
assert.equal(result.reason, 'bypass-rate-limit-gate');
});

test('evaluateKeepaliveLoop force_retry bypasses failed gate', async () => {
Expand Down
11 changes: 8 additions & 3 deletions .github/scripts/keepalive_loop.js
Original file line number Diff line number Diff line change
Expand Up @@ -1129,8 +1129,13 @@ async function evaluateKeepaliveLoop({ github, context, core, payload: overrideP
runId: gateRun.runId,
core,
});
// forceRetry bypasses defer/wait for cancelled gates
if (forceRetry && tasksRemaining) {
// Rate limits are infrastructure noise, not code quality issues
// Proceed with work if Gate only failed due to rate limits
if (gateRateLimit && tasksRemaining) {
action = 'run';
reason = 'bypass-rate-limit-gate';
if (core) core.info('Gate cancelled due to rate limits only - proceeding with work');
} else if (forceRetry && tasksRemaining) {
action = 'run';
reason = 'force-retry-cancelled';
if (core) core.info(`Force retry enabled: bypassing cancelled gate (rate_limit=${gateRateLimit})`);
Comment on lines +1134 to 1141
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

The new rate limit bypass logic takes precedence over the forceRetry flag. When both gateRateLimit and forceRetry are true, this code will return action='run' with reason='bypass-rate-limit-gate' instead of reason='force-retry-cancelled'. This changes the behavior for existing test case at line 493-517 in keepalive-loop.test.js which expects reason='force-retry-cancelled' when forceRetry is enabled with a rate-limited cancellation.

Consider checking forceRetry first (swap the order of these two conditions) to preserve the existing forceRetry behavior and maintain backward compatibility with the existing test expectations.

Suggested change
if (gateRateLimit && tasksRemaining) {
action = 'run';
reason = 'bypass-rate-limit-gate';
if (core) core.info('Gate cancelled due to rate limits only - proceeding with work (rate limits are not code quality issues)');
} else if (forceRetry && tasksRemaining) {
action = 'run';
reason = 'force-retry-cancelled';
if (core) core.info(`Force retry enabled: bypassing cancelled gate (rate_limit=${gateRateLimit})`);
if (forceRetry && tasksRemaining) {
action = 'run';
reason = 'force-retry-cancelled';
if (core) core.info(`Force retry enabled: bypassing cancelled gate (rate_limit=${gateRateLimit})`);
} else if (gateRateLimit && tasksRemaining) {
action = 'run';
reason = 'bypass-rate-limit-gate';
if (core) core.info('Gate cancelled due to rate limits only - proceeding with work (rate limits are not code quality issues)');

Copilot uses AI. Check for mistakes.
Expand All @@ -1139,7 +1144,7 @@ async function evaluateKeepaliveLoop({ github, context, core, payload: overrideP
reason = gateRateLimit ? 'gate-cancelled-rate-limit' : 'gate-cancelled';
}
} else {
// Gate failed - check if we should route to fix mode or wait
// Gate failed - check if failure is rate-limit related vs code quality
const gateFailure = await classifyGateFailure({ github, context, pr, core });
if (gateFailure.shouldFixMode && gateNormalized === 'failure') {
action = 'fix';
Expand Down
Loading
Loading