fix(primer): repair Claude binary path and re-anchor Codex window after reset
Two evidence-based fixes for the usage-window primer that aligns the 5h reset to fixed KST slots (08/13/18/23): 1. Claude primer always failed with binary_missing: it only looked under runners/agent-runner/node_modules, but the SDK platform binary is hoisted to the repo-root node_modules. Resolve both locations. 2. When a Codex slot lands while the 5h window is still active/exhausted, the warm-up call fails with a rate-limit and the window only re-anchors at its natural reset — leaving Codex un-primed until the next 5h slot. Parse the account's known reset time and schedule a one-shot retry right after it so the next window anchors close to the slot. Also corrected a stale comment claiming a 04:00–08:00 gap is enforced by message-runtime-gating (it is not). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -12,8 +12,13 @@ const refreshActiveCodexUsage = vi.fn(async () => {
|
||||
});
|
||||
const runCodexWarmupCycle = vi.fn(async () => {
|
||||
callOrder.push('warmup');
|
||||
return { status: 'warmed', accountIndex: 0 };
|
||||
return { status: 'warmed', accountIndex: 0 } as {
|
||||
status: string;
|
||||
accountIndex?: number;
|
||||
reason?: string;
|
||||
};
|
||||
});
|
||||
const getAllCodexAccounts = vi.fn<() => Array<{ resetAt?: string }>>(() => []);
|
||||
|
||||
vi.mock('./config.js', () => ({
|
||||
CODEX_WARMUP_CONFIG: {
|
||||
@@ -53,6 +58,10 @@ vi.mock('./codex-warmup.js', () => ({
|
||||
runCodexWarmupCycle,
|
||||
}));
|
||||
|
||||
vi.mock('./codex-token-rotation.js', () => ({
|
||||
getAllCodexAccounts,
|
||||
}));
|
||||
|
||||
describe('usage-primer', () => {
|
||||
it('refreshes Codex usage before primer selection and fires regardless of usage level', async () => {
|
||||
callOrder.length = 0;
|
||||
@@ -74,4 +83,31 @@ describe('usage-primer', () => {
|
||||
{ ignoreZeroUsageWindow: true },
|
||||
);
|
||||
});
|
||||
|
||||
it('reschedules a retry at the window reset when the slot is rate-limited', async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date('2026-06-09T00:00:00.000Z'));
|
||||
runCodexWarmupCycle.mockClear();
|
||||
// First slot: the 5h window is still active/exhausted → command fails.
|
||||
runCodexWarmupCycle.mockResolvedValueOnce({
|
||||
status: 'failed',
|
||||
accountIndex: 0,
|
||||
reason: 'exit_1',
|
||||
});
|
||||
// The account's window resets 60s from now.
|
||||
getAllCodexAccounts.mockReturnValue([
|
||||
{ resetAt: '2026-06-09T00:01:00.000Z' },
|
||||
]);
|
||||
|
||||
const { runCodexPrimerCycle } = await import('./usage-primer.js');
|
||||
await runCodexPrimerCycle();
|
||||
expect(runCodexWarmupCycle).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Retry is scheduled at reset (+60s) plus a 30s buffer = +90s.
|
||||
await vi.advanceTimersByTimeAsync(91_000);
|
||||
expect(runCodexWarmupCycle).toHaveBeenCalledTimes(2);
|
||||
|
||||
getAllCodexAccounts.mockReturnValue([]);
|
||||
vi.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user