From aac9a2a0986cf985c2db0b533a4d1e05ee0c02ef Mon Sep 17 00:00:00 2001 From: Yukun Shan <92423096+nateEc@users.noreply.github.com> Date: Fri, 4 Sep 2026 09:16:35 +0800 Subject: [PATCH 1/3] fix(codex): accept rate limit errors on thread resume (#8897) (cherry picked from commit 75ab5ab3fb6ad35117da754644c404a31b2fed84) --- .../scripts/generate.ts | 37 +++++++++++++++- .../src/_generated/schema.gen.ts | 6 +++ .../src/schema.test.ts | 44 +++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/packages/effect-codex-app-server/scripts/generate.ts b/packages/effect-codex-app-server/scripts/generate.ts index b1373a63d..23c18aacd 100644 --- a/packages/effect-codex-app-server/scripts/generate.ts +++ b/packages/effect-codex-app-server/scripts/generate.ts @@ -184,6 +184,40 @@ const Codex0150DefinitionSchemas: Record = { }, }; +function applyCodex0151DefinitionCompatibility( + exportName: string, + definitionName: string, + definitionSchema: Schema.Json, +): Schema.Json { + const isThreadResponse = + exportName === "V2ThreadReadResponse" || + exportName === "V2ThreadResumeResponse" || + exportName === "V2ThreadRollbackResponse"; + if ( + !isThreadResponse || + definitionName !== "CodexErrorInfo" || + typeof definitionSchema !== "object" + ) { + return definitionSchema; + } + + const schema = definitionSchema as { + readonly oneOf?: ReadonlyArray<{ readonly enum?: ReadonlyArray }>; + }; + const [firstVariant, ...remainingVariants] = schema.oneOf ?? []; + if (!firstVariant?.enum || firstVariant.enum.includes("rateLimitExceeded")) { + return definitionSchema; + } + + return { + ...definitionSchema, + oneOf: [ + { ...firstVariant, enum: [...firstVariant.enum, "rateLimitExceeded"] }, + ...remainingVariants, + ], + }; +} + const getGeneratedPaths = Effect.fn("getGeneratedPaths")(function* () { const path = yield* Path.Path; const generatedDir = path.join(import.meta.dirname, "..", "src", "_generated"); @@ -596,7 +630,8 @@ const generateFiles = Effect.fn("generateFiles")(function* () { for (const [definitionName, definitionSchema] of Object.entries(parsed.definitions ?? {})) { const compatibleDefinitionSchema = - Codex0150DefinitionSchemas[definitionName] ?? definitionSchema; + Codex0150DefinitionSchemas[definitionName] ?? + applyCodex0151DefinitionCompatibility(file.exportName, definitionName, definitionSchema); aggregateSchemas[localDefinitionNames.get(definitionName)!] = stripNullDefaults( normalizeNullableTypes( rewriteExternalRefs( diff --git a/packages/effect-codex-app-server/src/_generated/schema.gen.ts b/packages/effect-codex-app-server/src/_generated/schema.gen.ts index 4fe22549f..651c5921d 100644 --- a/packages/effect-codex-app-server/src/_generated/schema.gen.ts +++ b/packages/effect-codex-app-server/src/_generated/schema.gen.ts @@ -16676,6 +16676,7 @@ export type V2ThreadReadResponse__CodexErrorInfo = | "badRequest" | "threadRollbackFailed" | "sandboxError" + | "rateLimitExceeded" | "other" | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } } | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } } @@ -16699,6 +16700,7 @@ export const V2ThreadReadResponse__CodexErrorInfo = Schema.Union( "badRequest", "threadRollbackFailed", "sandboxError", + "rateLimitExceeded", "other", ]), Schema.Struct({ @@ -17029,6 +17031,7 @@ export type V2ThreadResumeResponse__CodexErrorInfo = | "badRequest" | "threadRollbackFailed" | "sandboxError" + | "rateLimitExceeded" | "other" | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } } | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } } @@ -17052,6 +17055,7 @@ export const V2ThreadResumeResponse__CodexErrorInfo = Schema.Union( "badRequest", "threadRollbackFailed", "sandboxError", + "rateLimitExceeded", "other", ]), Schema.Struct({ @@ -17307,6 +17311,7 @@ export type V2ThreadRollbackResponse__CodexErrorInfo = | "badRequest" | "threadRollbackFailed" | "sandboxError" + | "rateLimitExceeded" | "other" | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } } | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } } @@ -17330,6 +17335,7 @@ export const V2ThreadRollbackResponse__CodexErrorInfo = Schema.Union( "badRequest", "threadRollbackFailed", "sandboxError", + "rateLimitExceeded", "other", ]), Schema.Struct({ diff --git a/packages/effect-codex-app-server/src/schema.test.ts b/packages/effect-codex-app-server/src/schema.test.ts index a020d88fd..7b65474aa 100644 --- a/packages/effect-codex-app-server/src/schema.test.ts +++ b/packages/effect-codex-app-server/src/schema.test.ts @@ -5,6 +5,9 @@ import * as CodexSchema from "./schema.ts"; const isGetAccountResponse = Schema.is(CodexSchema.V2GetAccountResponse); const isAccountPlanType = Schema.is(CodexSchema.V2GetAccountResponse__PlanType); +const isThreadReadResponse = Schema.is(CodexSchema.V2ThreadReadResponse); +const isThreadResumeResponse = Schema.is(CodexSchema.V2ThreadResumeResponse); +const isThreadRollbackResponse = Schema.is(CodexSchema.V2ThreadRollbackResponse); it("accepts Codex 0.150 multi-agent values", () => { const schemas = [ @@ -75,6 +78,47 @@ it("accepts Codex 0.150 multi-agent values", () => { assert.equal(Schema.is(CodexSchema.V2ThreadResumeResponse)(resumeResponse), true); }); +it("accepts Codex rate limit errors for thread responses", () => { + const failedThread = { + cliVersion: "0.150.0", + createdAt: 0, + cwd: "/tmp/project", + ephemeral: false, + id: "thread-1", + modelProvider: "openai", + preview: "", + sessionId: "session-1", + source: "cli", + status: { type: "idle" }, + turns: [ + { + error: { + codexErrorInfo: "rateLimitExceeded", + message: "Rate limit exceeded", + }, + id: "turn-1", + items: [], + status: "failed", + }, + ], + updatedAt: 0, + }; + assert.equal(isThreadReadResponse({ thread: failedThread }), true); + assert.equal( + isThreadResumeResponse({ + approvalPolicy: "never", + approvalsReviewer: "user", + cwd: "/tmp/project", + model: "gpt-5.6-sol", + modelProvider: "openai", + sandbox: { type: "dangerFullAccess" }, + thread: failedThread, + }), + true, + ); + assert.equal(isThreadRollbackResponse({ thread: failedThread }), true); +}); + it("accepts Codex 0.150 account plan values", () => { const planTypes = [ "self_serve_business_prolite", From 220038956a89fc1e5fb8f22202a66155f24a1125 Mon Sep 17 00:00:00 2001 From: Trevor Walker Date: Sat, 5 Sep 2026 19:41:25 -0600 Subject: [PATCH 2/3] docs(upstream): record Codex quota history adoption --- .agents/upstream-review.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.agents/upstream-review.md b/.agents/upstream-review.md index 5ba4d4963..651b48afa 100644 --- a/.agents/upstream-review.md +++ b/.agents/upstream-review.md @@ -15,6 +15,24 @@ Two standing sections outlive any single batch and must be read on every review: ## Review batches +## 2026-09-05 — Codex quota history (A1, partial) + +Approved by the maintainer as part of A1–A4 against upstream +`f12d39359f0f76a64ff2d77959c5baf821df15be`. Adopted #8897 +(`75ab5ab3fb6ad35117da754644c404a31b2fed84`) as `eaca53d47f` on +`upstream/2026-09-05-codex-rate-limit`, awaiting PR merge. + +Thread read, resume, and rollback now accept `rateLimitExceeded` in Codex +history. The generator retains the compatibility extension. Kept Pylon's +account-plan coverage and excluded unrelated upstream async-question fixtures. +Four schema tests, package typecheck, and targeted lint pass. This changes the +server's Codex protocol decoder; all clients and connection modes benefit, +with no Pylon wire-contract or other-provider changes. + +The full review cursor is unchanged: this is one selected item from the +555-commit inventory. Deferred/watch outcomes are recorded with the Claude +portion of this batch (#268); no register or watch row is changed here. + ## 2026-09-02 — `9b2d04317c68233782e0630464ac86d77d0686f3..beae2147a9487ec47ac992319f2216914b4cb62d` The maintainer's standing instruction for this batch was to stop escalating From fb5d8fdca7c5e2463a207e1c5ef176b5f5ac737b Mon Sep 17 00:00:00 2001 From: Trevor Walker Date: Sat, 5 Sep 2026 20:32:51 -0600 Subject: [PATCH 3/3] docs(upstream): consolidate batch records in PR #279 --- .agents/upstream-review.md | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/.agents/upstream-review.md b/.agents/upstream-review.md index 651b48afa..5ba4d4963 100644 --- a/.agents/upstream-review.md +++ b/.agents/upstream-review.md @@ -15,24 +15,6 @@ Two standing sections outlive any single batch and must be read on every review: ## Review batches -## 2026-09-05 — Codex quota history (A1, partial) - -Approved by the maintainer as part of A1–A4 against upstream -`f12d39359f0f76a64ff2d77959c5baf821df15be`. Adopted #8897 -(`75ab5ab3fb6ad35117da754644c404a31b2fed84`) as `eaca53d47f` on -`upstream/2026-09-05-codex-rate-limit`, awaiting PR merge. - -Thread read, resume, and rollback now accept `rateLimitExceeded` in Codex -history. The generator retains the compatibility extension. Kept Pylon's -account-plan coverage and excluded unrelated upstream async-question fixtures. -Four schema tests, package typecheck, and targeted lint pass. This changes the -server's Codex protocol decoder; all clients and connection modes benefit, -with no Pylon wire-contract or other-provider changes. - -The full review cursor is unchanged: this is one selected item from the -555-commit inventory. Deferred/watch outcomes are recorded with the Claude -portion of this batch (#268); no register or watch row is changed here. - ## 2026-09-02 — `9b2d04317c68233782e0630464ac86d77d0686f3..beae2147a9487ec47ac992319f2216914b4cb62d` The maintainer's standing instruction for this batch was to stop escalating