[codex] Recover Codex context overflow sessions
This commit is contained in:
@@ -21,6 +21,23 @@ import { getErrorMessage } from './utils.js';
|
||||
|
||||
type AttemptResult = 'success' | 'error';
|
||||
|
||||
function isRetryableCodexSessionFailureAttempt(args: {
|
||||
provider: 'claude' | 'codex';
|
||||
attempt: MessageAgentAttempt;
|
||||
}): boolean {
|
||||
const { provider, attempt } = args;
|
||||
if (provider !== 'codex' || attempt.sawOutput) return false;
|
||||
if (attempt.retryableSessionFailureDetected === true) return true;
|
||||
if (attempt.output != null)
|
||||
return shouldRetryFreshCodexSessionOnAgentFailure(attempt.output);
|
||||
return attempt.error == null
|
||||
? false
|
||||
: shouldRetryFreshCodexSessionOnAgentFailure({
|
||||
result: null,
|
||||
error: getErrorMessage(attempt.error),
|
||||
});
|
||||
}
|
||||
|
||||
export async function executeMessageAgentAttemptLifecycle(args: {
|
||||
provider: 'claude' | 'codex';
|
||||
runAttempt: (provider: 'claude' | 'codex') => Promise<MessageAgentAttempt>;
|
||||
@@ -212,34 +229,13 @@ export async function executeMessageAgentAttemptLifecycle(args: {
|
||||
};
|
||||
};
|
||||
|
||||
const isRetryableCodexSessionFailure = (
|
||||
attempt: MessageAgentAttempt,
|
||||
): boolean => {
|
||||
if (provider !== 'codex' || attempt.sawOutput) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (attempt.retryableSessionFailureDetected === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (attempt.error == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return shouldRetryFreshCodexSessionOnAgentFailure({
|
||||
result: null,
|
||||
error: getErrorMessage(attempt.error),
|
||||
});
|
||||
};
|
||||
|
||||
const recoverRetryableCodexSessionFailure = async (
|
||||
attempt: MessageAgentAttempt,
|
||||
): Promise<{
|
||||
attempt: MessageAgentAttempt;
|
||||
resolved: AttemptResult | null;
|
||||
}> => {
|
||||
if (!isRetryableCodexSessionFailure(attempt)) {
|
||||
if (!isRetryableCodexSessionFailureAttempt({ provider, attempt })) {
|
||||
return { attempt, resolved: null };
|
||||
}
|
||||
|
||||
@@ -250,7 +246,12 @@ export async function executeMessageAgentAttemptLifecycle(args: {
|
||||
);
|
||||
|
||||
const freshAttempt = await runTrackedAttempt('codex');
|
||||
if (!isRetryableCodexSessionFailure(freshAttempt)) {
|
||||
if (
|
||||
!isRetryableCodexSessionFailureAttempt({
|
||||
provider,
|
||||
attempt: freshAttempt,
|
||||
})
|
||||
) {
|
||||
return { attempt: freshAttempt, resolved: null };
|
||||
}
|
||||
|
||||
|
||||
@@ -3461,7 +3461,7 @@ describe('runAgentForGroup Claude rotation', () => {
|
||||
expect(deps.clearSession).toHaveBeenCalledWith('test-claude');
|
||||
});
|
||||
|
||||
it('drops a poisoned Codex session id before retrying a fresh session after remote compaction failure', async () => {
|
||||
it('drops a poisoned Codex session id before retrying after context-window overflow', async () => {
|
||||
const group = {
|
||||
...makeGroup(),
|
||||
folder: 'test-codex',
|
||||
@@ -3494,20 +3494,19 @@ describe('runAgentForGroup Claude rotation', () => {
|
||||
vi.mocked(agentRunner.runAgentProcess)
|
||||
.mockImplementationOnce(async (_group, input) => {
|
||||
expect(input.sessionId).toBe('stale-codex-session-id');
|
||||
throw new Error(
|
||||
"Error running remote compact task: Unknown parameter: 'prompt_cache_retention'",
|
||||
);
|
||||
return {
|
||||
status: 'error',
|
||||
result: null,
|
||||
error: "Codex ran out of room in the model's context window.",
|
||||
newSessionId: 'stale-codex-session-id',
|
||||
};
|
||||
})
|
||||
.mockImplementationOnce(async (_group, input, _onProcess, onOutput) => {
|
||||
expect(input.sessionId).toBeUndefined();
|
||||
await onOutput?.({
|
||||
status: 'success',
|
||||
phase: 'final',
|
||||
result: 'fresh Codex retry success',
|
||||
});
|
||||
await onOutput?.({ status: 'success', phase: 'final', result: 'ok' });
|
||||
return {
|
||||
status: 'success',
|
||||
result: 'fresh Codex retry success',
|
||||
result: 'ok',
|
||||
newSessionId: 'fresh-codex-session-id',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -135,6 +135,17 @@ describe('shouldRetryFreshCodexSessionOnAgentFailure', () => {
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('retries Codex context-window overflow with a fresh session', () => {
|
||||
const output = {
|
||||
result: null,
|
||||
error:
|
||||
"Codex ran out of room in the model's context window.\nStart a new thread or clear earlier history before retrying.",
|
||||
};
|
||||
|
||||
expect(shouldResetCodexSessionOnAgentFailure(output)).toBe(true);
|
||||
expect(shouldRetryFreshCodexSessionOnAgentFailure(output)).toBe(true);
|
||||
});
|
||||
|
||||
it('does not retry generic Codex Bad Request signals during observation-only rollout', () => {
|
||||
const output = {
|
||||
result: null,
|
||||
|
||||
@@ -20,6 +20,8 @@ const SESSION_RETRY_PATTERNS = [
|
||||
|
||||
const CODEX_SESSION_RESET_PATTERNS = [
|
||||
/Error running remote compact task/i,
|
||||
/Codex ran out of room in the model's context window/i,
|
||||
/Start a new thread or clear earlier history before retrying/i,
|
||||
/prompt_cache_retention/i,
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user