authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-24 11:55:32-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-24 11:55:32-07:00
logffb5fcce7336f9b3d5bde3a5f496a816907663af
treed91016d0f112daa07df5eca721d335f59b76516c
parentf26ee083fe41720207fb10616359500dcecc19d3
signature Signed by PGP key B5690EEEBB952194

fix(server): keep Codex's reset answer when the re-probe fails (#13363)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

3 files changed, 58 insertions(+), 6 deletions(-)

apps/server/src/provider/Drivers/CodexDriver.ts+8-5
......@@ -315,16 +315,19 @@ export const CodexDriver: ProviderDriver<CodexSettings, CodexDriverEnv> = {
315315 // The windows just changed; re-probe so the snapshot says so. A
316316 // failed probe republishes the pre-redemption limits rather than
317317 // marking them failed, so "confirmed" means `checkedAt` moved
318 // past what was published before the redemption started.
319 Effect.tap(() =>
318 // past what was published before the redemption started. Only a
319 // reset claims the limits changed, so only a reset reports an
320 // unconfirmed refresh.
321 Effect.tap((outcome) =>
320322 Effect.gen(function* () {
321323 const before = (yield* snapshot.getSnapshot).usageLimits?.checkedAt;
322324 const refreshed = yield* snapshot.refresh;
323325 const after = refreshed.usageLimits?.checkedAt;
324326 if (
325 after === undefined ||
326 after === before ||
327 refreshed.usageLimits?.unavailable?.reason === "probeFailed"
327 outcome === "reset" &&
328 (after === undefined ||
329 after === before ||
330 refreshed.usageLimits?.unavailable?.reason === "probeFailed")
328331 ) {
329332 return yield* new ProviderDriverError({
330333 driver: DRIVER_KIND,
apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts+38
......@@ -338,6 +338,44 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => {
338338 }).pipe(Effect.provide(testLayer)),
339339 );
340340
341 it.live("reports Codex's answer when a redemption changed nothing", () =>
342 Effect.gen(function* () {
343 if (yield* isHostWindows) return;
344 const fileSystem = yield* FileSystem.FileSystem;
345 const fixtures = yield* makeTildeProviderFixtures();
346 yield* fileSystem.writeFileString(
347 fixtures.codexScriptPath,
348 // @effect-diagnostics-next-line preferSchemaOverJson:off - fixed script document read by the external Codex mock peer.
349 JSON.stringify({
350 rootThreadId: "probe-thread",
351 notifications: [],
352 account: { type: "chatgpt", email: "test@example.com", planType: "plus" },
353 failRateLimitsRead: true,
354 resetCreditOutcome: "alreadyRedeemed",
355 }),
356 );
357 const codexId = ProviderInstanceId.make("codex_reset");
358 const { registry } = yield* makeProviderInstanceRegistry({
359 drivers: [CodexDriver],
360 configMap: {
361 [codexId]: {
362 driver: ProviderDriverKind.make("codex"),
363 enabled: true,
364 environment: [
365 { name: "T3_CODEX_COLLAB_SCRIPT", value: fixtures.codexScriptPath, sensitive: false },
366 ],
367 config: makeCodexConfig({ enabled: true, binaryPath: fixtures.codexBinaryPath }),
368 },
369 },
370 });
371 const codex = yield* registry.getInstance(codexId);
372 expect(codex).toBeDefined();
373 // The usage read fails, so the re-probe cannot confirm new limits.
374 yield* codex!.snapshot.refresh;
375 expect(yield* codex!.consumeResetCredit!()).toBe("alreadyRedeemed");
376 }).pipe(Effect.provide(testLayer)),
377 );
378
341379 it.live("runs Codex and Claude readiness probes from configured tilde paths", () =>
342380 Effect.gen(function* () {
343381 if (yield* isHostWindows) return;
apps/server/src/provider/testFixtures/codexCollabMockPeer.mjs+12-1
......@@ -84,7 +84,18 @@ rl.on("line", (line) => {
8484 return;
8585 }
8686 if (method === "account/read") {
87 write({ id, result: { account: { type: "apiKey" }, requiresOpenaiAuth: false } });
87 write({
88 id,
89 result: { account: script.account ?? { type: "apiKey" }, requiresOpenaiAuth: false },
90 });
91 return;
92 }
93 if (method === "account/rateLimits/read" && script.failRateLimitsRead) {
94 write({ id, error: { code: -32000, message: "usage unavailable" } });
95 return;
96 }
97 if (method === "account/rateLimitResetCredit/consume" && script.resetCreditOutcome) {
98 write({ id, result: { outcome: script.resetCreditOutcome } });
8899 return;
89100 }
90101 if (method === "skills/list" || method === "model/list") {