Skip to content

Commit b3f818c

Browse files
committed
Cover the gate exemption and the parallel-tool-call exemption together
Both are independent guards feeding the same silentPastThreshold check in the stall watchdog. Add pure and bridge-level tests asserting each exempts alone and that neither's guard accidentally requires the other's condition to also hold.
1 parent f61d574 commit b3f818c

2 files changed

Lines changed: 43 additions & 0 deletions

File tree

src/tui-opentui/stall-watchdog.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,19 @@ describe("shouldAbortForStall", () => {
5050
expect(shouldAbortForStall({ ...base, status: "stopping" })).toBe(false)
5151
})
5252

53+
// Two independent exemptions (a gate open on the operator, a sibling tool
54+
// call still outstanding) must both keep exempting when combined — neither
55+
// one's guard may accidentally require the other's condition to also hold.
56+
test("a gate open and a sibling tool call each exempt alone, and together", () => {
57+
const gateOnly = { ...base, status: "blocked" as const }
58+
const toolCallOnly = { ...base, activeToolCalls: ["call-2"] }
59+
const both = { ...base, status: "blocked" as const, activeToolCalls: ["call-2"] }
60+
61+
expect(shouldAbortForStall(gateOnly)).toBe(false)
62+
expect(shouldAbortForStall(toolCallOnly)).toBe(false)
63+
expect(shouldAbortForStall(both)).toBe(false)
64+
})
65+
5366
test("a settled turn with nothing in flight is not a stall", () => {
5467
expect(
5568
shouldAbortForStall({

src/tui-opentui/turn-monitor.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,36 @@ describe("stall watchdog", () => {
410410
}
411411
})
412412
})
413+
414+
// The gate exemption (this fix) and the parallel-tool-call exemption
415+
// (CL-5641) are independent guards feeding the same stall check — a run
416+
// with both outstanding must stay exempt, and closing the gate while the
417+
// tool call is still out must not re-expose it to the clock.
418+
test("a gate open alongside a live sibling tool call stays exempt", async () => {
419+
await withTestRenderer(async (h) => {
420+
const t: Harness = await setup(h)
421+
try {
422+
t.bridge.submit("build it", "immediate")
423+
t.bridge.handle({
424+
type: "inference.tool_call.end",
425+
data: { name: "task", callId: "c1" },
426+
})
427+
t.bridge.gateOpened()
428+
t.port.clear()
429+
430+
t.advance(20 * 60_000)
431+
t.tick()
432+
expect(t.port.calls).toEqual([])
433+
434+
t.bridge.gateClosed()
435+
t.advance(20 * 60_000)
436+
t.tick()
437+
expect(t.port.calls).toEqual([])
438+
} finally {
439+
t.bridge.dispose()
440+
}
441+
})
442+
})
413443
})
414444

415445
describe("repetition guard", () => {

0 commit comments

Comments
 (0)