Skip to content

Commit ceb7a4a

Browse files
Darren2030claudevincentkoc
authored
fix(codex): project guardianWarning circuit-breaker notification (#101220)
* fix(codex): project guardianWarning circuit-breaker notification Codex emits a `guardianWarning` notification from its rejection circuit-breaker (e.g. 3 consecutive or 10 total denials in a turn) right before ending the turn as interrupted. The app-server event projector had no case for it, so the rejection-limit reason was silently dropped and the turn degraded to a generic interruption. Project it on the `codex_app_server.guardian` stream with phase "warning", alongside the existing autoApprovalReview handling. The exact v2 param shape is not bundled in OpenClaw's protocol schemas, so the human-readable reason is read defensively. Closes #101207 Co-Authored-By: Claude Fable 5 <[email protected]> * fix(codex): route guardianWarning through thread-scoped filter `guardianWarning` is thread-scoped — codex emits it with only {message, threadId} (no turnId). The turn-strict pre-switch filter therefore dropped it before it reached the projection switch, making the handler added in the previous commit dead code for real traffic. Correlate it on the thread alone (mirroring hook notifications) so it reaches the switch, and read the `message` field per the codex schema. The regression test now sends the real {threadId, message} shape instead of a synthetic turnId, so it actually exercises the routing. Refs #101207 Co-Authored-By: Claude Fable 5 <[email protected]> * repro(codex): add real behavior proof for guardianWarning projection * fix(codex): narrow guardian warning projection Co-authored-by: 曾文锋0668000834 <[email protected]> --------- Co-authored-by: Claude Fable 5 <[email protected]> Co-authored-by: Vincent Koc <[email protected]>
1 parent c0b6b84 commit ceb7a4a

2 files changed

Lines changed: 42 additions & 0 deletions

File tree

extensions/codex/src/app-server/event-projector.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1652,6 +1652,30 @@ describe("CodexAppServerEventProjector", () => {
16521652
).toBe(false);
16531653
});
16541654

1655+
it("projects thread-scoped guardian warnings", async () => {
1656+
const onAgentEvent = vi.fn();
1657+
const projector = await createProjector({ ...(await createParams()), onAgentEvent });
1658+
1659+
await projector.handleNotification({
1660+
method: "guardianWarning",
1661+
params: { threadId: "thread-other", message: "Wrong thread." },
1662+
} as ProjectorNotification);
1663+
await projector.handleNotification({
1664+
method: "guardianWarning",
1665+
params: {
1666+
threadId: THREAD_ID,
1667+
message: "Guardian rejection limit reached; ending turn as interrupted.",
1668+
},
1669+
} as ProjectorNotification);
1670+
1671+
const warning = findAgentEvent(onAgentEvent, {
1672+
stream: "codex_app_server.guardian",
1673+
phase: "warning",
1674+
}).data;
1675+
expect(warning.message).toBe("Guardian rejection limit reached; ending turn as interrupted.");
1676+
expect(onAgentEvent).toHaveBeenCalledTimes(1);
1677+
});
1678+
16551679
it("projects reasoning end, plan updates, compaction state, and tool metadata", async () => {
16561680
const onReasoningStream = vi.fn();
16571681
const onReasoningEnd = vi.fn();

extensions/codex/src/app-server/event-projector.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,11 @@ export class CodexAppServerEventProjector {
684684
if (!this.isHookNotificationForCurrentThread(params)) {
685685
return;
686686
}
687+
} else if (notification.method === "guardianWarning") {
688+
// Codex guardian warnings are thread-scoped and carry no turn id.
689+
if (readCodexNotificationThreadId(params) !== this.threadId) {
690+
return;
691+
}
687692
} else if (!this.isNotificationForTurn(params)) {
688693
return;
689694
}
@@ -719,6 +724,9 @@ export class CodexAppServerEventProjector {
719724
case "item/autoApprovalReview/completed":
720725
this.handleGuardianReviewNotification(notification.method, params);
721726
break;
727+
case "guardianWarning":
728+
this.handleGuardianWarning(params);
729+
break;
722730
case "hook/started":
723731
case "hook/completed":
724732
this.handleHookNotification(notification.method, params);
@@ -1257,6 +1265,16 @@ export class CodexAppServerEventProjector {
12571265
});
12581266
}
12591267

1268+
private handleGuardianWarning(params: JsonObject): void {
1269+
this.emitAgentEvent({
1270+
stream: "codex_app_server.guardian",
1271+
data: {
1272+
phase: "warning",
1273+
message: readString(params, "message"),
1274+
},
1275+
});
1276+
}
1277+
12601278
private handleHookNotification(method: string, params: JsonObject): void {
12611279
const run = isJsonObject(params.run) ? params.run : undefined;
12621280
if (!run) {

0 commit comments

Comments
 (0)