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> = {
194194 },
195195};
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
197212function applyCodex0151DefinitionCompatibility(
198213 exportName: string,
199214 definitionName: string,
200215 definitionSchema: Schema.Json,
201216): Schema.Json {
202 const isThreadResponse =
203 exportName === "V2ThreadReadResponse" ||
204 exportName === "V2ThreadResumeResponse" ||
205 exportName === "V2ThreadRollbackResponse";
206217 if (
207 !isThreadResponse ||
218 !CodexErrorInfoCompatibilityExports.has(exportName) ||
208219 definitionName !== "CodexErrorInfo" ||
209220 typeof definitionSchema !== "object"
210221 ) {
......@@ -215,16 +226,28 @@ function applyCodex0151DefinitionCompatibility(
215226 readonly oneOf?: ReadonlyArray<{ readonly enum?: ReadonlyArray<string> }>;
216227 };
217228 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) {
219238 return definitionSchema;
220239 }
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
222248 return {
223249 ...definitionSchema,
224 oneOf: [
225 { ...firstVariant, enum: [...firstVariant.enum, "rateLimitExceeded"] },
226 ...remainingVariants,
227 ],
250 oneOf: [{ ...firstVariant, enum: nextEnum }, ...remainingVariants],
228251 };
229252}
230253
packages/effect-codex-app-server/src/_generated/schema.gen.ts+14
......@@ -15947,6 +15947,8 @@ export type V2ThreadForkResponse__CodexErrorInfo =
1594715947 | "badRequest"
1594815948 | "threadRollbackFailed"
1594915949 | "sandboxError"
15950 | "rateLimitExceeded"
15951 | "misalignmentPolicyViolation"
1595015952 | "other"
1595115953 | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } }
1595215954 | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } }
......@@ -15970,6 +15972,8 @@ export const V2ThreadForkResponse__CodexErrorInfo = Schema.Union(
1597015972 "badRequest",
1597115973 "threadRollbackFailed",
1597215974 "sandboxError",
15975 "rateLimitExceeded",
15976 "misalignmentPolicyViolation",
1597315977 "other",
1597415978 ]),
1597515979 Schema.Struct({
......@@ -16855,6 +16859,7 @@ export type V2ThreadReadResponse__CodexErrorInfo =
1685516859 | "threadRollbackFailed"
1685616860 | "sandboxError"
1685716861 | "rateLimitExceeded"
16862 | "misalignmentPolicyViolation"
1685816863 | "other"
1685916864 | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } }
1686016865 | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } }
......@@ -16879,6 +16884,7 @@ export const V2ThreadReadResponse__CodexErrorInfo = Schema.Union(
1687916884 "threadRollbackFailed",
1688016885 "sandboxError",
1688116886 "rateLimitExceeded",
16887 "misalignmentPolicyViolation",
1688216888 "other",
1688316889 ]),
1688416890 Schema.Struct({
......@@ -17210,6 +17216,7 @@ export type V2ThreadResumeResponse__CodexErrorInfo =
1721017216 | "threadRollbackFailed"
1721117217 | "sandboxError"
1721217218 | "rateLimitExceeded"
17219 | "misalignmentPolicyViolation"
1721317220 | "other"
1721417221 | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } }
1721517222 | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } }
......@@ -17234,6 +17241,7 @@ export const V2ThreadResumeResponse__CodexErrorInfo = Schema.Union(
1723417241 "threadRollbackFailed",
1723517242 "sandboxError",
1723617243 "rateLimitExceeded",
17244 "misalignmentPolicyViolation",
1723717245 "other",
1723817246 ]),
1723917247 Schema.Struct({
......@@ -17490,6 +17498,7 @@ export type V2ThreadRollbackResponse__CodexErrorInfo =
1749017498 | "threadRollbackFailed"
1749117499 | "sandboxError"
1749217500 | "rateLimitExceeded"
17501 | "misalignmentPolicyViolation"
1749317502 | "other"
1749417503 | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } }
1749517504 | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } }
......@@ -17514,6 +17523,7 @@ export const V2ThreadRollbackResponse__CodexErrorInfo = Schema.Union(
1751417523 "threadRollbackFailed",
1751517524 "sandboxError",
1751617525 "rateLimitExceeded",
17526 "misalignmentPolicyViolation",
1751717527 "other",
1751817528 ]),
1751917529 Schema.Struct({
......@@ -18713,6 +18723,8 @@ export type V2TurnCompletedNotification__CodexErrorInfo =
1871318723 | "badRequest"
1871418724 | "threadRollbackFailed"
1871518725 | "sandboxError"
18726 | "rateLimitExceeded"
18727 | "misalignmentPolicyViolation"
1871618728 | "other"
1871718729 | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } }
1871818730 | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } }
......@@ -18736,6 +18748,8 @@ export const V2TurnCompletedNotification__CodexErrorInfo = Schema.Union(
1873618748 "badRequest",
1873718749 "threadRollbackFailed",
1873818750 "sandboxError",
18751 "rateLimitExceeded",
18752 "misalignmentPolicyViolation",
1873918753 "other",
1874018754 ]),
1874118755 Schema.Struct({
packages/effect-codex-app-server/src/schema.test.ts+60
......@@ -7,6 +7,9 @@ const isGetAccountResponse = Schema.is(CodexSchema.V2GetAccountResponse);
77const isThreadReadResponse = Schema.is(CodexSchema.V2ThreadReadResponse);
88const isThreadResumeResponse = Schema.is(CodexSchema.V2ThreadResumeResponse);
99const isThreadRollbackResponse = Schema.is(CodexSchema.V2ThreadRollbackResponse);
10const isThreadForkResponse = Schema.is(CodexSchema.V2ThreadForkResponse);
11const isTurnCompletedNotification = Schema.is(CodexSchema.V2TurnCompletedNotification);
12const decodeThreadResumeResponse = Schema.decodeUnknownSync(CodexSchema.V2ThreadResumeResponse);
1013
1114it("keeps async questions in live notifications and thread history", () => {
1215 const item = {
......@@ -141,6 +144,63 @@ it("accepts Codex rate limit errors for thread responses", () => {
141144 assert.equal(isThreadRollbackResponse({ thread: failedThread }), true);
142145});
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
144204it("accepts Codex 0.150 account plan values", () => {
145205 const planTypes = [
146206 "self_serve_business_prolite",