mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:30:43 +00:00
fix(codex/app-server): release session lane when projector throws on turn/completed
This commit is contained in:
committed by
Peter Steinberger
parent
54a2a20447
commit
f2f27775fb
@@ -31,6 +31,8 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/compaction: always reload embedded Pi resources through an explicit loader and reapply reserve-token overrides so runs without extension factories no longer silently lose compaction settings before session start. (#67146) Thanks @ly85206559.
|
||||
- Memory-core/dreaming: normalize sweep timestamps and reuse hashed narrative session keys for fallback cleanup so Dreaming narrative sub-sessions stop leaking. (#67023) Thanks @chiyouYCH.
|
||||
- Gateway/startup: delay HTTP bind until websocket handlers are attached, so immediate post-startup websocket health/connect probes no longer hit the startup race window. (#43392) Thanks @dalefrieswthat.
|
||||
- Codex/app-server: release the session lane when a downstream consumer throws while draining the `turn/completed` notification, so follow-up messages after a Codex plugin reply stop queueing behind a stale lane lock. Fixes #67996. (#69072) Thanks @ayeshakhalid192007-dev.
|
||||
|
||||
## 2026.4.20
|
||||
|
||||
### Changes
|
||||
|
||||
@@ -295,6 +295,61 @@ describe("runCodexAppServerAttempt", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("releases completion when a projector callback throws during turn/completed", async () => {
|
||||
// Regression for openclaw/openclaw#67996: a throw inside the projector's
|
||||
// turn/completed handler must not strand resolveCompletion, otherwise the
|
||||
// gateway session lane stays locked and every follow-up message queues
|
||||
// behind a run that will never resolve.
|
||||
let notify: (notification: CodexServerNotification) => Promise<void> = async () => undefined;
|
||||
const request = vi.fn(async (method: string) => {
|
||||
if (method === "thread/start") {
|
||||
return { thread: { id: "thread-1" }, model: "gpt-5.4-codex", modelProvider: "openai" };
|
||||
}
|
||||
if (method === "turn/start") {
|
||||
return { turn: { id: "turn-1", status: "inProgress" } };
|
||||
}
|
||||
return {};
|
||||
});
|
||||
__testing.setCodexAppServerClientFactoryForTests(
|
||||
async () =>
|
||||
({
|
||||
request,
|
||||
addNotificationHandler: (handler: typeof notify) => {
|
||||
notify = handler;
|
||||
return () => undefined;
|
||||
},
|
||||
addRequestHandler: () => () => undefined,
|
||||
}) as never,
|
||||
);
|
||||
const params = createParams(
|
||||
path.join(tempDir, "session.jsonl"),
|
||||
path.join(tempDir, "workspace"),
|
||||
);
|
||||
params.onAgentEvent = () => {
|
||||
throw new Error("downstream consumer exploded");
|
||||
};
|
||||
const run = runCodexAppServerAttempt(params);
|
||||
await vi.waitFor(() =>
|
||||
expect(request.mock.calls.some(([method]) => method === "turn/start")).toBe(true),
|
||||
);
|
||||
await notify({
|
||||
method: "turn/completed",
|
||||
params: {
|
||||
threadId: "thread-1",
|
||||
turnId: "turn-1",
|
||||
turn: {
|
||||
id: "turn-1",
|
||||
status: "completed",
|
||||
items: [{ id: "plan-1", type: "plan", text: "step one\nstep two" }],
|
||||
},
|
||||
},
|
||||
});
|
||||
await expect(run).resolves.toMatchObject({
|
||||
aborted: false,
|
||||
timedOut: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("times out app-server startup before thread setup can hang forever", async () => {
|
||||
__testing.setCodexAppServerClientFactoryForTests(() => new Promise<never>(() => undefined));
|
||||
const params = createParams(
|
||||
|
||||
@@ -136,13 +136,23 @@ export async function runCodexAppServerAttempt(
|
||||
pendingNotifications.push(notification);
|
||||
return;
|
||||
}
|
||||
await projector.handleNotification(notification);
|
||||
if (
|
||||
notification.method === "turn/completed" &&
|
||||
isTurnNotification(notification.params, turnId)
|
||||
) {
|
||||
completed = true;
|
||||
resolveCompletion?.();
|
||||
// Determine terminal-turn status before invoking the projector so a throw
|
||||
// inside projector.handleNotification still releases the session lane.
|
||||
// See openclaw/openclaw#67996.
|
||||
const isTurnCompletion =
|
||||
notification.method === "turn/completed" && isTurnNotification(notification.params, turnId);
|
||||
try {
|
||||
await projector.handleNotification(notification);
|
||||
} catch (error) {
|
||||
embeddedAgentLog.debug("codex app-server projector notification threw", {
|
||||
method: notification.method,
|
||||
error,
|
||||
});
|
||||
} finally {
|
||||
if (isTurnCompletion) {
|
||||
completed = true;
|
||||
resolveCompletion?.();
|
||||
}
|
||||
}
|
||||
};
|
||||
const enqueueNotification = (notification: CodexServerNotification): Promise<void> => {
|
||||
|
||||
Reference in New Issue
Block a user