authorgravatar for bakarimussa06@gmail.comBakari Mustafa <bakarimussa06@gmail.com> 2026-09-07 09:49:00+10:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-06 16:49:00-07:00
log95139254ba98fb8ad12a001ebcbc9d4b833ead19
tree29d8996da2ab9f222e56756e6c2d6f96bfa527f2
parentd6aa179ad2afe0424e1410cbafbb119bbd5f7809
signature Signed by PGP key B5690EEEBB952194

fix(codex): accept misalignment policy errors on thread resume (#10373)


3 files changed, 107 insertions(+), 10 deletions(-)

packages/effect-codex-app-server/scripts/generate.ts+33-10
...@@ -194,17 +194,28 @@ const Codex0150DefinitionSchemas: Record<string, Schema.Json> = {...@@ -194,17 +194,28 @@ const Codex0150DefinitionSchemas: Record<string, Schema.Json> = {
194 },194 },
195};195};
196196
197// Pinned protocol JSON omits later CodexErrorInfo variants. Keep historical
198// thread payloads decodable; do not fold unknown values into "other".
199const CodexErrorInfoCompatibilityValues = [
200 "rateLimitExceeded",
201 "misalignmentPolicyViolation",
202] as const;
203
204const CodexErrorInfoCompatibilityExports = new Set([
205 "V2ThreadReadResponse",
206 "V2ThreadResumeResponse",
207 "V2ThreadRollbackResponse",
208 "V2ThreadForkResponse",
209 "V2TurnCompletedNotification",
210]);
211
197function applyCodex0151DefinitionCompatibility(212function applyCodex0151DefinitionCompatibility(
198 exportName: string,213 exportName: string,
199 definitionName: string,214 definitionName: string,
200 definitionSchema: Schema.Json,215 definitionSchema: Schema.Json,
201): Schema.Json {216): Schema.Json {
202 const isThreadResponse =
203 exportName === "V2ThreadReadResponse" ||
204 exportName === "V2ThreadResumeResponse" ||
205 exportName === "V2ThreadRollbackResponse";
206 if (217 if (
207 !isThreadResponse ||218 !CodexErrorInfoCompatibilityExports.has(exportName) ||
208 definitionName !== "CodexErrorInfo" ||219 definitionName !== "CodexErrorInfo" ||
209 typeof definitionSchema !== "object"220 typeof definitionSchema !== "object"
210 ) {221 ) {
...@@ -215,16 +226,28 @@ function applyCodex0151DefinitionCompatibility(...@@ -215,16 +226,28 @@ function applyCodex0151DefinitionCompatibility(
215 readonly oneOf?: ReadonlyArray<{ readonly enum?: ReadonlyArray<string> }>;226 readonly oneOf?: ReadonlyArray<{ readonly enum?: ReadonlyArray<string> }>;
216 };227 };
217 const [firstVariant, ...remainingVariants] = schema.oneOf ?? [];228 const [firstVariant, ...remainingVariants] = schema.oneOf ?? [];
218 if (!firstVariant?.enum || firstVariant.enum.includes("rateLimitExceeded")) {229 const currentEnum = firstVariant?.enum;
230 if (!currentEnum) {
231 return definitionSchema;
232 }
233
234 const missingValues = CodexErrorInfoCompatibilityValues.filter(
235 (value) => !currentEnum.includes(value),
236 );
237 if (missingValues.length === 0) {
219 return definitionSchema;238 return definitionSchema;
220 }239 }
221240
241 const enumValues = [...currentEnum];
242 const otherIndex = enumValues.indexOf("other");
243 const nextEnum =
244 otherIndex === -1
245 ? [...enumValues, ...missingValues]
246 : [...enumValues.slice(0, otherIndex), ...missingValues, ...enumValues.slice(otherIndex)];
247
222 return {248 return {
223 ...definitionSchema,249 ...definitionSchema,
224 oneOf: [250 oneOf: [{ ...firstVariant, enum: nextEnum }, ...remainingVariants],
225 { ...firstVariant, enum: [...firstVariant.enum, "rateLimitExceeded"] },
226 ...remainingVariants,
227 ],
228 };251 };
229}252}
230253
packages/effect-codex-app-server/src/_generated/schema.gen.ts+14
...@@ -15947,6 +15947,8 @@ export type V2ThreadForkResponse__CodexErrorInfo =...@@ -15947,6 +15947,8 @@ export type V2ThreadForkResponse__CodexErrorInfo =
15947 | "badRequest"15947 | "badRequest"
15948 | "threadRollbackFailed"15948 | "threadRollbackFailed"
15949 | "sandboxError"15949 | "sandboxError"
15950 | "rateLimitExceeded"
15951 | "misalignmentPolicyViolation"
15950 | "other"15952 | "other"
15951 | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } }15953 | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } }
15952 | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } }15954 | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } }
...@@ -15970,6 +15972,8 @@ export const V2ThreadForkResponse__CodexErrorInfo = Schema.Union(...@@ -15970,6 +15972,8 @@ export const V2ThreadForkResponse__CodexErrorInfo = Schema.Union(
15970 "badRequest",15972 "badRequest",
15971 "threadRollbackFailed",15973 "threadRollbackFailed",
15972 "sandboxError",15974 "sandboxError",
15975 "rateLimitExceeded",
15976 "misalignmentPolicyViolation",
15973 "other",15977 "other",
15974 ]),15978 ]),
15975 Schema.Struct({15979 Schema.Struct({
...@@ -16855,6 +16859,7 @@ export type V2ThreadReadResponse__CodexErrorInfo =...@@ -16855,6 +16859,7 @@ export type V2ThreadReadResponse__CodexErrorInfo =
16855 | "threadRollbackFailed"16859 | "threadRollbackFailed"
16856 | "sandboxError"16860 | "sandboxError"
16857 | "rateLimitExceeded"16861 | "rateLimitExceeded"
16862 | "misalignmentPolicyViolation"
16858 | "other"16863 | "other"
16859 | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } }16864 | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } }
16860 | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } }16865 | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } }
...@@ -16879,6 +16884,7 @@ export const V2ThreadReadResponse__CodexErrorInfo = Schema.Union(...@@ -16879,6 +16884,7 @@ export const V2ThreadReadResponse__CodexErrorInfo = Schema.Union(
16879 "threadRollbackFailed",16884 "threadRollbackFailed",
16880 "sandboxError",16885 "sandboxError",
16881 "rateLimitExceeded",16886 "rateLimitExceeded",
16887 "misalignmentPolicyViolation",
16882 "other",16888 "other",
16883 ]),16889 ]),
16884 Schema.Struct({16890 Schema.Struct({
...@@ -17210,6 +17216,7 @@ export type V2ThreadResumeResponse__CodexErrorInfo =...@@ -17210,6 +17216,7 @@ export type V2ThreadResumeResponse__CodexErrorInfo =
17210 | "threadRollbackFailed"17216 | "threadRollbackFailed"
17211 | "sandboxError"17217 | "sandboxError"
17212 | "rateLimitExceeded"17218 | "rateLimitExceeded"
17219 | "misalignmentPolicyViolation"
17213 | "other"17220 | "other"
17214 | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } }17221 | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } }
17215 | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } }17222 | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } }
...@@ -17234,6 +17241,7 @@ export const V2ThreadResumeResponse__CodexErrorInfo = Schema.Union(...@@ -17234,6 +17241,7 @@ export const V2ThreadResumeResponse__CodexErrorInfo = Schema.Union(
17234 "threadRollbackFailed",17241 "threadRollbackFailed",
17235 "sandboxError",17242 "sandboxError",
17236 "rateLimitExceeded",17243 "rateLimitExceeded",
17244 "misalignmentPolicyViolation",
17237 "other",17245 "other",
17238 ]),17246 ]),
17239 Schema.Struct({17247 Schema.Struct({
...@@ -17490,6 +17498,7 @@ export type V2ThreadRollbackResponse__CodexErrorInfo =...@@ -17490,6 +17498,7 @@ export type V2ThreadRollbackResponse__CodexErrorInfo =
17490 | "threadRollbackFailed"17498 | "threadRollbackFailed"
17491 | "sandboxError"17499 | "sandboxError"
17492 | "rateLimitExceeded"17500 | "rateLimitExceeded"
17501 | "misalignmentPolicyViolation"
17493 | "other"17502 | "other"
17494 | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } }17503 | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } }
17495 | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } }17504 | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } }
...@@ -17514,6 +17523,7 @@ export const V2ThreadRollbackResponse__CodexErrorInfo = Schema.Union(...@@ -17514,6 +17523,7 @@ export const V2ThreadRollbackResponse__CodexErrorInfo = Schema.Union(
17514 "threadRollbackFailed",17523 "threadRollbackFailed",
17515 "sandboxError",17524 "sandboxError",
17516 "rateLimitExceeded",17525 "rateLimitExceeded",
17526 "misalignmentPolicyViolation",
17517 "other",17527 "other",
17518 ]),17528 ]),
17519 Schema.Struct({17529 Schema.Struct({
...@@ -18713,6 +18723,8 @@ export type V2TurnCompletedNotification__CodexErrorInfo =...@@ -18713,6 +18723,8 @@ export type V2TurnCompletedNotification__CodexErrorInfo =
18713 | "badRequest"18723 | "badRequest"
18714 | "threadRollbackFailed"18724 | "threadRollbackFailed"
18715 | "sandboxError"18725 | "sandboxError"
18726 | "rateLimitExceeded"
18727 | "misalignmentPolicyViolation"
18716 | "other"18728 | "other"
18717 | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } }18729 | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } }
18718 | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } }18730 | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } }
...@@ -18736,6 +18748,8 @@ export const V2TurnCompletedNotification__CodexErrorInfo = Schema.Union(...@@ -18736,6 +18748,8 @@ export const V2TurnCompletedNotification__CodexErrorInfo = Schema.Union(
18736 "badRequest",18748 "badRequest",
18737 "threadRollbackFailed",18749 "threadRollbackFailed",
18738 "sandboxError",18750 "sandboxError",
18751 "rateLimitExceeded",
18752 "misalignmentPolicyViolation",
18739 "other",18753 "other",
18740 ]),18754 ]),
18741 Schema.Struct({18755 Schema.Struct({
packages/effect-codex-app-server/src/schema.test.ts+60
...@@ -7,6 +7,9 @@ const isGetAccountResponse = Schema.is(CodexSchema.V2GetAccountResponse);...@@ -7,6 +7,9 @@ const isGetAccountResponse = Schema.is(CodexSchema.V2GetAccountResponse);
7const isThreadReadResponse = Schema.is(CodexSchema.V2ThreadReadResponse);7const isThreadReadResponse = Schema.is(CodexSchema.V2ThreadReadResponse);
8const isThreadResumeResponse = Schema.is(CodexSchema.V2ThreadResumeResponse);8const isThreadResumeResponse = Schema.is(CodexSchema.V2ThreadResumeResponse);
9const isThreadRollbackResponse = Schema.is(CodexSchema.V2ThreadRollbackResponse);9const isThreadRollbackResponse = Schema.is(CodexSchema.V2ThreadRollbackResponse);
10const isThreadForkResponse = Schema.is(CodexSchema.V2ThreadForkResponse);
11const isTurnCompletedNotification = Schema.is(CodexSchema.V2TurnCompletedNotification);
12const decodeThreadResumeResponse = Schema.decodeUnknownSync(CodexSchema.V2ThreadResumeResponse);
1013
11it("keeps async questions in live notifications and thread history", () => {14it("keeps async questions in live notifications and thread history", () => {
12 const item = {15 const item = {
...@@ -141,6 +144,63 @@ it("accepts Codex rate limit errors for thread responses", () => {...@@ -141,6 +144,63 @@ it("accepts Codex rate limit errors for thread responses", () => {
141 assert.equal(isThreadRollbackResponse({ thread: failedThread }), true);144 assert.equal(isThreadRollbackResponse({ thread: failedThread }), true);
142});145});
143146
147it("accepts Codex misalignment policy errors for thread responses", () => {
148 const failedThread = {
149 cliVersion: "0.150.0",
150 createdAt: 0,
151 cwd: "/tmp/project",
152 ephemeral: false,
153 id: "thread-1",
154 modelProvider: "openai",
155 preview: "",
156 sessionId: "session-1",
157 source: "cli",
158 status: { type: "idle" },
159 turns: [
160 {
161 error: {
162 codexErrorInfo: "misalignmentPolicyViolation",
163 message: "Misalignment policy violation",
164 },
165 id: "turn-1",
166 items: [],
167 status: "failed",
168 },
169 ],
170 updatedAt: 0,
171 };
172 const resumeLikeResponse = {
173 approvalPolicy: "never",
174 approvalsReviewer: "user",
175 cwd: "/tmp/project",
176 model: "gpt-5.6-sol",
177 modelProvider: "openai",
178 sandbox: { type: "dangerFullAccess" },
179 thread: failedThread,
180 };
181 assert.equal(isThreadReadResponse({ thread: failedThread }), true);
182 assert.equal(isThreadResumeResponse(resumeLikeResponse), true);
183 assert.equal(isThreadRollbackResponse({ thread: failedThread }), true);
184 assert.equal(isThreadForkResponse(resumeLikeResponse), true);
185 const decodedResume = decodeThreadResumeResponse(resumeLikeResponse);
186 assert.equal(decodedResume.thread.turns[0]?.error?.codexErrorInfo, "misalignmentPolicyViolation");
187 assert.equal(
188 isTurnCompletedNotification({
189 threadId: "thread-1",
190 turn: {
191 error: {
192 codexErrorInfo: "misalignmentPolicyViolation",
193 message: "Misalignment policy violation",
194 },
195 id: "turn-1",
196 items: [],
197 status: "failed",
198 },
199 }),
200 true,
201 );
202});
203
144it("accepts Codex 0.150 account plan values", () => {204it("accepts Codex 0.150 account plan values", () => {
145 const planTypes = [205 const planTypes = [
146 "self_serve_business_prolite",206 "self_serve_business_prolite",