detect owner codex bad request loops

This commit is contained in:
ejclaw
2026-04-29 22:20:06 +09:00
parent 8297057e5a
commit 6c6b67daca
11 changed files with 545 additions and 0 deletions

View File

@@ -1,6 +1,7 @@
import { Database } from 'bun:sqlite';
import { normalizeServiceId } from '../config.js';
import { CODEX_BAD_REQUEST_DETAIL_JSON } from '../codex-bad-request-signal.js';
import type { PairedTurnIdentity } from '../paired-turn-identity.js';
import { inferAgentTypeFromServiceShadow } from '../role-service-shadow.js';
import type { AgentType, PairedRoomRole } from '../types.js';
@@ -33,6 +34,13 @@ export interface PairedTurnAttemptRecord {
last_error: string | null;
}
export interface OwnerCodexBadRequestFailureSummary {
taskId: string;
failures: number;
firstFailureAt: string;
latestFailureAt: string;
}
function resolveExecutorMetadata(args: {
executorServiceId?: string | null;
executorAgentType?: AgentType | null;
@@ -423,6 +431,59 @@ export function getPairedTurnAttemptIdFromDatabase(
return row?.attempt_id ?? null;
}
export function getOwnerCodexBadRequestFailureSummaryForTaskFromDatabase(
database: Database,
args: {
taskId: string;
threshold: number;
},
): OwnerCodexBadRequestFailureSummary | null {
const threshold = Math.max(1, Math.floor(args.threshold));
const attempts = database
.prepare(
`
SELECT state, last_error, created_at
FROM paired_turn_attempts
WHERE task_id = ?
AND role = 'owner'
AND executor_agent_type = 'codex'
ORDER BY created_at DESC, attempt_no DESC
`,
)
.all(args.taskId) as Array<{
state: PairedTurnAttemptState;
last_error: string | null;
created_at: string;
}>;
const consecutiveFailures = [];
for (const attempt of attempts) {
if (
attempt.state === 'failed' &&
attempt.last_error?.trim() === CODEX_BAD_REQUEST_DETAIL_JSON
) {
consecutiveFailures.push(attempt);
continue;
}
break;
}
if (consecutiveFailures.length < threshold) {
return null;
}
const firstFailureAt =
consecutiveFailures[consecutiveFailures.length - 1].created_at;
const latestFailureAt = consecutiveFailures[0].created_at;
return {
taskId: args.taskId,
failures: consecutiveFailures.length,
firstFailureAt,
latestFailureAt,
};
}
export function setPairedTurnAttemptContinuationHandoffIdInDatabase(
database: Database,
args: {

View File

@@ -43,7 +43,9 @@ import {
} from './paired-state.js';
import {
clearPairedTurnAttemptsInDatabase,
getOwnerCodexBadRequestFailureSummaryForTaskFromDatabase,
getPairedTurnAttemptsForTurnFromDatabase,
type OwnerCodexBadRequestFailureSummary,
type PairedTurnAttemptRecord,
} from './paired-turn-attempts.js';
import {
@@ -237,6 +239,16 @@ export function getPairedTurnAttempts(
return getPairedTurnAttemptsForTurnFromDatabase(requireDatabase(), turnId);
}
export function getOwnerCodexBadRequestFailureSummaryForTask(args: {
taskId: string;
threshold: number;
}): OwnerCodexBadRequestFailureSummary | null {
return getOwnerCodexBadRequestFailureSummaryForTaskFromDatabase(
requireDatabase(),
args,
);
}
export function upsertPairedWorkspace(workspace: PairedWorkspace): void {
upsertPairedWorkspaceInDatabase(requireDatabase(), workspace);
}