Skip to content

Commit 9a18c3d

Browse files
Copilotpelikhan
andauthored
fix: stop codex retries when thread already has active goal
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
1 parent 46dcdb9 commit 9a18c3d

4 files changed

Lines changed: 25 additions & 2 deletions

File tree

actions/setup/js/codex_harness.cjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,10 +439,11 @@ async function main() {
439439
}
440440

441441
const nonRetryableGuard = detectNonRetryableHarnessGuard(result.output);
442-
if (nonRetryableGuard.aiCreditsExceeded || nonRetryableGuard.awfAPIProxyBlockingRequests) {
442+
if (nonRetryableGuard.aiCreditsExceeded || nonRetryableGuard.awfAPIProxyBlockingRequests || nonRetryableGuard.goalAlreadyActive) {
443443
const reasons = [];
444444
if (nonRetryableGuard.aiCreditsExceeded) reasons.push("AI credits budget exceeded");
445445
if (nonRetryableGuard.awfAPIProxyBlockingRequests) reasons.push("AWF API proxy is blocking requests");
446+
if (nonRetryableGuard.goalAlreadyActive) reasons.push("goal is already active for this thread");
446447
log(`attempt ${attempt + 1}: ${reasons.join(" and ")} — not retrying (non-retryable guard condition)`);
447448
break;
448449
}

actions/setup/js/codex_harness.test.cjs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,9 +409,11 @@ env_key = "OPENAI_API_KEY"
409409
if (result.exitCode === 0) return false;
410410
const RATE_LIMIT_ERROR_PATTERN = /rate_limit_exceeded|429 Too Many Requests|RateLimitError/i;
411411
const SERVER_ERROR_PATTERN = /InternalServerError|ServiceUnavailableError|500 Internal Server Error|503 Service Unavailable/i;
412+
const GOAL_ALREADY_ACTIVE_PATTERN = /cannot create a new goal because this thread already has a goal|this thread already has a goal|use update_goal only when the existing goal is complete/i;
412413
if (attempt === 0 && isAuthenticationFailedError(result.output)) return false;
413414
if (isMissingApiKeyError(result.output)) return false;
414415
if (hasNumerousPermissionDeniedIssues(result.output)) return false;
416+
if (GOAL_ALREADY_ACTIVE_PATTERN.test(result.output)) return false;
415417
const isTransient = RATE_LIMIT_ERROR_PATTERN.test(result.output) || SERVER_ERROR_PATTERN.test(result.output);
416418
return attempt < MAX_RETRIES && (result.hasOutput || isTransient);
417419
}
@@ -461,6 +463,15 @@ env_key = "OPENAI_API_KEY"
461463
const result = { exitCode: 1, hasOutput: true, output: "permission denied\npermission denied\npermission denied" };
462464
expect(shouldRetry(result, 0)).toBe(false);
463465
});
466+
467+
it("does not retry when codex reports an existing active goal", () => {
468+
const result = {
469+
exitCode: 1,
470+
hasOutput: true,
471+
output: "cannot create a new goal because this thread already has a goal; use update_goal only when the existing goal is complete",
472+
};
473+
expect(shouldRetry(result, 0)).toBe(false);
474+
});
464475
});
465476

466477
describe("noop pre-flight and retry guard", () => {

actions/setup/js/harness_retry_guard.cjs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@
55
const AI_CREDITS_EXCEEDED_PATTERNS = [/\bmax[\s_-]*ai[\s_-]*credits[\s_-]*exceeded\b/i, /\bai[\s_-]*credits[\s_-]*rate[\s_-]*limit[\s_-]*error\b/i, /ai[\s_-]*credits?.*(?:rate[\s-]*limit|limit exceeded|budget exceeded|exceeded)/i];
66

77
const AWF_API_PROXY_BLOCKING_REQUESTS_PATTERNS = [/\bawf\b.*\bapi[\s_-]*proxy\b.*\bblocking requests\b/i, /\bapi[\s_-]*proxy\b.*\bblocking requests\b/i, /\bapi[\s_-]*proxy\b.*\bblocked requests?\b/i, /\bDIFC_FILTERED\b/];
8+
const GOAL_ALREADY_ACTIVE_PATTERNS = [/\bcannot create a new goal because this thread already has a goal\b/i, /\bthis thread already has a goal\b/i, /\buse update_goal only when the existing goal is complete\b/i];
89

910
/**
1011
* Detect retry guard conditions that should stop harness retries immediately.
1112
* @param {unknown} output
12-
* @returns {{ aiCreditsExceeded: boolean, awfAPIProxyBlockingRequests: boolean }}
13+
* @returns {{ aiCreditsExceeded: boolean, awfAPIProxyBlockingRequests: boolean, goalAlreadyActive: boolean }}
1314
*/
1415
function detectNonRetryableHarnessGuard(output) {
1516
const safeOutput = typeof output === "string" ? output : "";
1617
return {
1718
aiCreditsExceeded: AI_CREDITS_EXCEEDED_PATTERNS.some(pattern => pattern.test(safeOutput)),
1819
awfAPIProxyBlockingRequests: AWF_API_PROXY_BLOCKING_REQUESTS_PATTERNS.some(pattern => pattern.test(safeOutput)),
20+
goalAlreadyActive: GOAL_ALREADY_ACTIVE_PATTERNS.some(pattern => pattern.test(safeOutput)),
1921
};
2022
}
2123

@@ -24,5 +26,6 @@ if (typeof module !== "undefined" && module.exports) {
2426
detectNonRetryableHarnessGuard,
2527
AI_CREDITS_EXCEEDED_PATTERNS,
2628
AWF_API_PROXY_BLOCKING_REQUESTS_PATTERNS,
29+
GOAL_ALREADY_ACTIVE_PATTERNS,
2730
};
2831
}

actions/setup/js/harness_retry_guard.test.cjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,13 @@ describe("harness_retry_guard.cjs", () => {
6565
const result = detectNonRetryableHarnessGuard("transient network timeout");
6666
expect(result.aiCreditsExceeded).toBe(false);
6767
expect(result.awfAPIProxyBlockingRequests).toBe(false);
68+
expect(result.goalAlreadyActive).toBe(false);
69+
});
70+
71+
it("detects goal already active markers", () => {
72+
const result = detectNonRetryableHarnessGuard("cannot create a new goal because this thread already has a goal; use update_goal only when the existing goal is complete");
73+
expect(result.aiCreditsExceeded).toBe(false);
74+
expect(result.awfAPIProxyBlockingRequests).toBe(false);
75+
expect(result.goalAlreadyActive).toBe(true);
6876
});
6977
});

0 commit comments

Comments
 (0)