Skip to content

Commit e55633c

Browse files
committed
test(tui): pin no-timeout approval gates to wait indefinitely
1 parent 690f6dc commit e55633c

2 files changed

Lines changed: 54 additions & 0 deletions

File tree

‎plans/CL9713-status.md‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# CL-9713 — Decision record: cut custom watchdog back to native HITL baseline
2+
3+
**Status:** Resolved (won't-build / wont-fix) per operator decision.
4+
**Date:** Reverted from working tree; working tree back to HEAD baseline + pre-existing env noise.
5+
6+
## Decision
7+
8+
CL-9713 resolved per operator decision: rely on native Interchange HITL approve/confirm. All custom watchdog machinery shipped by CL-9713 (per-gate `openGates` metadata, `stuckState`/`lastProgressAt` on TurnState, `gateOpened`/`gateClosed` re-keyed by gateId, `bag.gateById`, `approvalTimeout()`/watchdog-settings mapper, `classifyStuckLevel`/`toolCallLastProgressAt`, `stuckStateRampPhase`/`stuckState` UI surfacing, watchdog telemetry capture/recovery/cancel, watchdog recovery coordinator, watchdog e2e/settings/recovery tests, runner wiring, and docs edits) has been removed from the tree. `approvalTimeout()` stays unarmed — it returns `undefined` in the baseline. Functional outcome: **native Interchange HITL, no custom watchdog.**
9+
10+
- **Approval gates wait indefinitely for the operator**: `approvalTimeout()` returns `undefined` (src/tui/runner/session.ts), so gate-wire autoDeny/autoCancel timers arm only `if (ev.timeoutMs !== undefined)`; no product path ever sets `timeoutMs`, so no auto-settle in product.
11+
- **Exit + resume reloads pending approvals**: `PendingOperation` / `resolveParkedCallIdFromStore` (src/session/approval-resume.ts:93-104) re-parks pending approvals on the resumed generation — existing baseline behavior, verified.
12+
- **Pinning test**: a `gate with no timeout waits indefinitely` test was added to src/tui/gate-wire.test.ts (permission + operator cases). Baseline already armed timers only when `timeoutMs` was provided, but no test asserted the no-timeout case, so the guard was added.
13+
- **Recommendation**: close the Linear CL-9713 issue as wont-fix/wont-build with this rationale.
14+
- **Related**: see CL-10234 (HITL approval gates wait indefinitely — pin behavior + decision record) and PR #1361 (bwachman) for the actual bug fix pending review fleet, recommended for release 0.3.36.
15+
16+
## Removed / reverted
17+
18+
- Reverted to HEAD (git checkout): docs (ARCHITECTURE, IMPLEMENTATION, PRODUCT, TUI), settings.ts + settings.test.ts, telemetry (index, product-events, + tests), chrome-state.ts, gate-wire.ts (+ test), product-host.ts, runner (host, index, session, state), runtime-bridge.ts (+ test), stall-watchdog.ts (+ test), turn-state.ts (+ test), agent-ask-wake / allow-once-reprompt / ramp-paint / turn-monitor tests (gateId threading).
19+
- Deleted (untracked CL-9713 files): src/tui/watchdog-recovery.ts, src/tui/watchdog-recovery.test.ts, src/tui/watchdog.e2e.test.ts, src/config/watchdog-settings.test.ts.
20+
21+
## Kept (pre-existing baseline, MUST stay)
22+
23+
stall-watchdog.ts, tool-execution-watchdog.ts, run-liveness.ts, budget-race.ts, `MAX_TOOL_EXECUTION_TIMEOUT_MS`, `STALL_TIMEOUT_MS`. vendor/ zero diff.

‎src/tui/gate-wire.test.ts‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,6 +1084,37 @@ describe("operator.gate auto-cancel", () => {
10841084
});
10851085
});
10861086

1087+
describe("permission/operator gate with no timeout waits indefinitely", () => {
1088+
test("an unanswered permission gate with no timeoutMs never auto-denies", async () => {
1089+
await withGates(async ({ shell, emitter }) => {
1090+
const settled = settleCapture();
1091+
emitPermission(emitter, { resolve: settled.resolve });
1092+
expect(shell.overlayKind).toBe("permissions");
1093+
1094+
// No timeoutMs was provided, so no auto-deny timer may be armed.
1095+
// After a long wait the gate must still be open and unresolved.
1096+
await new Promise((r) => setTimeout(r, 50));
1097+
expect(settled.get()).toBeUndefined();
1098+
expect(shell.overlayKind).toBe("permissions");
1099+
});
1100+
});
1101+
1102+
test("an unanswered operator gate with no timeoutMs never auto-cancels", async () => {
1103+
await withGates(async ({ shell, emitter }) => {
1104+
const settled = settleCapture();
1105+
emitOperator(emitter, {
1106+
options: ["Yes", "No"],
1107+
resolve: settled.resolve,
1108+
});
1109+
expect(shell.overlayKind).toBe("operator");
1110+
1111+
await new Promise((r) => setTimeout(r, 50));
1112+
expect(settled.get()).toBeUndefined();
1113+
expect(shell.overlayKind).toBe("operator");
1114+
});
1115+
});
1116+
});
1117+
10871118
describe("Esc on a gate overlay settles the awaited promise", () => {
10881119
test("permission.gate: Esc denies instead of abandoning the promise", async () => {
10891120
await withGates(async ({ shell, emitter }) => {

0 commit comments

Comments
 (0)