Skip to content

Commit dac685f

Browse files
committed
fix(skill-workshop): extend Codex dynamic-tool timeout for lifecycle approvals
skill_workshop lifecycle approvals (apply/reject/quarantine) require up to 120s plus 10s gateway grace (130s total), but the generic Codex dynamic-tool watchdog fires at 90s. The watchdog fired first with a generic timeout error, leaving the approval pending and the proposal unmodified. Add a 150s per-tool deadline for skill_workshop in readConfiguredDynamicToolTimeoutMs, matching the pattern used for message and image tools. The approval can now complete or expire cleanly before the Codex watchdog fires; non-Codex callers keep the unchanged 120s approval window. Fixes #91266
1 parent da40134 commit dac685f

4 files changed

Lines changed: 46 additions & 0 deletions

File tree

extensions/codex/src/app-server/dynamic-tool-execution.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
44
import {
55
CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS,
66
CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS,
7+
CODEX_DYNAMIC_SKILL_WORKSHOP_TOOL_TIMEOUT_MS,
78
CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS,
89
CODEX_DYNAMIC_TOOL_TIMEOUT_MS,
910
handleDynamicToolCallWithTimeout,
@@ -123,6 +124,24 @@ describe("dynamic tool execution helpers", () => {
123124
).toBe(CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS);
124125
});
125126

127+
it("uses an extended deadline for all skill_workshop calls regardless of action", () => {
128+
for (const action of ["apply", "reject", "quarantine", "list", "inspect"]) {
129+
expect(
130+
resolveDynamicToolCallTimeoutMs({
131+
call: {
132+
threadId: "thread-1",
133+
turnId: "turn-1",
134+
callId: `call-skill-workshop-${action}`,
135+
namespace: null,
136+
tool: "skill_workshop",
137+
arguments: { action, proposal_id: "weather-20260530-a1b2c3d4e5" },
138+
},
139+
config: undefined,
140+
}),
141+
).toBe(CODEX_DYNAMIC_SKILL_WORKSHOP_TOOL_TIMEOUT_MS);
142+
}
143+
});
144+
126145
it("uses media image config and caps excessive dynamic tool timeouts", () => {
127146
expect(
128147
resolveDynamicToolCallTimeoutMs({

extensions/codex/src/app-server/dynamic-tool-execution.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ const CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS = 120_000;
2828
export const CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS = 60_000;
2929
/** Timeout for message-delivery dynamic tool calls. */
3030
export const CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS = 120_000;
31+
// any skill_workshop action may become a lifecycle call after before_tool_call hooks; approval waits up to 130s.
32+
export const CODEX_DYNAMIC_SKILL_WORKSHOP_TOOL_TIMEOUT_MS = 150_000;
3133
const LOG_FIELD_MAX_LENGTH = 160;
3234

3335
type DynamicToolTimeoutDetails = {
@@ -414,6 +416,10 @@ function readConfiguredDynamicToolTimeoutMs(
414416
return CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS;
415417
}
416418

419+
if (toolName === "skill_workshop") {
420+
return CODEX_DYNAMIC_SKILL_WORKSHOP_TOOL_TIMEOUT_MS;
421+
}
422+
417423
return undefined;
418424
}
419425

extensions/codex/src/app-server/side-question.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1213,6 +1213,22 @@ describe("runCodexAppServerSideQuestion", () => {
12131213
expect(timeoutMs).toBe(90_000);
12141214
});
12151215

1216+
it("uses extended 150s for all side-thread skill_workshop calls regardless of action", () => {
1217+
for (const action of ["apply", "reject", "quarantine", "list", "inspect"]) {
1218+
const timeoutMs = testing.resolveSideDynamicToolCallTimeoutMs({
1219+
call: {
1220+
threadId: "side-thread",
1221+
turnId: "turn-1",
1222+
callId: `tool-skill-workshop-${action}`,
1223+
tool: "skill_workshop",
1224+
arguments: { action, proposal_id: "weather-20260530-a1b2c3d4e5" },
1225+
},
1226+
config: {} as never,
1227+
});
1228+
expect(timeoutMs).toBe(150_000);
1229+
}
1230+
});
1231+
12161232
it("cleans up notification handlers when side tool setup fails", async () => {
12171233
const client = createFakeClient();
12181234
createOpenClawCodingToolsMock.mockImplementation(() => {

extensions/codex/src/app-server/side-question.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ const CODEX_SIDE_DYNAMIC_TOOL_TIMEOUT_MS = 90_000;
8383
const CODEX_SIDE_DYNAMIC_TOOL_MAX_TIMEOUT_MS = 600_000;
8484
const CODEX_SIDE_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS = 120_000;
8585
const CODEX_SIDE_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS = 60_000;
86+
// any skill_workshop action may become a lifecycle call after before_tool_call hooks; approval waits up to 130s.
87+
const CODEX_SIDE_DYNAMIC_SKILL_WORKSHOP_TOOL_TIMEOUT_MS = 150_000;
8688
const SIDE_QUESTION_COMPLETION_TIMEOUT_MS = 600_000;
8789
const CODEX_SIDE_NATIVE_HOOK_RELAY_MIN_TTL_MS = 30 * 60_000;
8890
const CODEX_SIDE_NATIVE_HOOK_RELAY_TTL_GRACE_MS = 5 * 60_000;
@@ -706,6 +708,9 @@ function resolveSideDynamicToolCallTimeoutMs(params: {
706708
(params.call.tool === "image"
707709
? (readSideTimeoutSecondsAsMs(params.config?.tools?.media?.image?.timeoutSeconds) ??
708710
CODEX_SIDE_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS)
711+
: undefined) ??
712+
(params.call.tool === "skill_workshop"
713+
? CODEX_SIDE_DYNAMIC_SKILL_WORKSHOP_TOOL_TIMEOUT_MS
709714
: undefined);
710715
return clampSideDynamicToolTimeoutMs(configured ?? CODEX_SIDE_DYNAMIC_TOOL_TIMEOUT_MS);
711716
}

0 commit comments

Comments
 (0)