Skip to content

Commit 6be7297

Browse files
committed
fix(codex): narrow quiescent turn completion stall handling
1 parent 4049ecf commit 6be7297

2 files changed

Lines changed: 16 additions & 17 deletions

File tree

extensions/codex/src/app-server/run-attempt.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2458,7 +2458,7 @@ describe("runCodexAppServerAttempt", () => {
24582458
});
24592459
});
24602460

2461-
it("times out promptly when the last completed current-turn item is not followed by turn completion", async () => {
2461+
it("times out promptly when the last completed non-assistant current-turn item is not followed by turn completion", async () => {
24622462
let notify: (notification: CodexServerNotification) => Promise<void> = async () => undefined;
24632463
const request = vi.fn(async (method: string) => {
24642464
if (method === "thread/start") {
@@ -2469,7 +2469,7 @@ describe("runCodexAppServerAttempt", () => {
24692469
}
24702470
return {};
24712471
});
2472-
__testing.setCodexAppServerClientFactoryForTests(
2472+
setCodexAppServerClientFactoryForTest(
24732473
async () =>
24742474
({
24752475
request,

extensions/codex/src/app-server/run-attempt.ts

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,10 +1254,16 @@ export async function runCodexAppServerAttempt(
12541254
turnAssistantCompletionIdleWatchArmed &&
12551255
notification.method === "item/completed" &&
12561256
activeTurnItemIds.size === 0;
1257-
const shouldKeepCompletionIdleWatchArmed =
1257+
const trackedDynamicToolCompletion = isTrackedOpenClawDynamicToolCompletionNotification(
1258+
notification,
1259+
activeOpenClawDynamicToolCallIds,
1260+
);
1261+
const shouldRearmCompletionIdleWatchAfterLastCurrentTurnItem =
12581262
isCurrentTurnNotification &&
12591263
notification.method === "item/completed" &&
1260-
activeTurnItemIds.size === 0;
1264+
activeTurnItemIds.size === 0 &&
1265+
!trackedDynamicToolCompletion &&
1266+
!isCompletedAssistantNotification(notification);
12611267
if (isCurrentTurnNotification && notification.method === "error") {
12621268
if (isRetryableErrorNotification(notification.params)) {
12631269
disarmTurnCompletionIdleWatch();
@@ -1271,14 +1277,10 @@ export async function runCodexAppServerAttempt(
12711277
armTurnAssistantCompletionIdleWatch(describeNotificationActivity(notification));
12721278
} else if (unblockedAssistantCompletionRelease) {
12731279
armTurnAssistantCompletionIdleWatch(describeNotificationActivity(notification));
1274-
} else if (
1275-
isCurrentTurnNotification &&
1276-
notification.method === "item/completed" &&
1277-
activeTurnItemIds.size === 0
1278-
) {
1279-
// If the bridge goes silent immediately after the last current-turn item
1280-
// completes, do not wait for the long terminal watchdog. Re-arm the short
1281-
// completion-idle guard so we fail promptly instead of wedging the session.
1280+
} else if (shouldRearmCompletionIdleWatchAfterLastCurrentTurnItem) {
1281+
// If a non-assistant current-turn item is the last active item and the
1282+
// bridge then goes quiet, reset the short completion-idle guard from that
1283+
// final completion so the remaining silent-turn gap fails fast.
12821284
armTurnCompletionIdleWatch();
12831285
} else if (
12841286
isCurrentTurnNotification &&
@@ -1291,11 +1293,8 @@ export async function runCodexAppServerAttempt(
12911293
!turnCompletionIdleWatchPinnedByTerminalError &&
12921294
notification.method !== "turn/completed" &&
12931295
isCurrentTurnNotification &&
1294-
!isTrackedOpenClawDynamicToolCompletionNotification(
1295-
notification,
1296-
activeOpenClawDynamicToolCallIds,
1297-
) &&
1298-
!shouldKeepCompletionIdleWatchArmed
1296+
!trackedDynamicToolCompletion &&
1297+
!shouldRearmCompletionIdleWatchAfterLastCurrentTurnItem
12991298
) {
13001299
// The short completion-idle watchdog guards blind gaps after Codex
13011300
// accepts a turn or after OpenClaw hands a turn-scoped request result

0 commit comments

Comments
 (0)