Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions windows/src-tauri/src/pipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,16 @@ async fn handle(app: AppHandle, mut pipe: impl Relay) {

// No decision: say nothing at all. coucou-hook then writes nothing to stdout
// and Claude Code asks in the terminal, exactly as if Coucou were closed.
if let Some(d) = decision {
let _ = pipe.write_all(format!("{d}\n").as_bytes()).await;
let _ = pipe.flush().await;
// The island is told, so a card that came up late (or is still up after the
// long wait) goes instead of holding the view with nobody to answer (#857).
match decision {
Some(d) => {
let _ = pipe.write_all(format!("{d}\n").as_bytes()).await;
let _ = pipe.flush().await;
}
None => {
let _ = app.emit_to(WINDOW_LABEL, "approval-expired", &id);
}
}
pipe.finish();
}
Expand Down
8 changes: 8 additions & 0 deletions windows/src/island/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,14 @@ export function registerHookHandlers(island: Island) {
// The last plan numbers seen survive a restart, as on the Mac.
State.planUsage ??= restorePlanUsage(storedClaudePlanUsage());
void onEvent<HookPayload>("hook", (payload) => handleHook(island, payload));
// The relay stopped waiting without a decision and the terminal has taken
// over: a card still up for that request would be lying (#857).
void onEvent<string>("approval-expired", (requestId) => {
if (State.pendingApproval?.requestId !== requestId) return;
if (pendingTimeout != null) window.clearTimeout(pendingTimeout);
pendingTimeout = null;
dropPendingCard(island);
});
}

function handleHook(island: Island, payload: HookPayload) {
Expand Down
15 changes: 15 additions & 0 deletions windows/tests/hooks.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,21 @@ test("the end of the turn takes a waiting card down and releases the relay", ()
}
});

test("a card the relay stopped waiting for goes, and the next request gets its card (#857)", () => {
hook({ hook_event_name: "PermissionRequest", request_id: "r1", session_id: "s1", tool_name: "Bash" });
// Another request's expiry leaves this card alone.
emit("approval-expired", "r0");
assert.equal(State.pendingApproval?.requestId, "r1");
emit("approval-expired", "r1");
assert.equal(State.pendingApproval, null);
hook({ hook_event_name: "PermissionRequest", request_id: "r2", session_id: "s1", tool_name: "Bash" });
assert.equal(State.pendingApproval?.requestId, "r2");
assert.deepEqual(sent("approval_decline"), []);
// r1's 110 s timer was cancelled: it doesn't take r2's card down.
seconds(100);
assert.equal(State.pendingApproval?.requestId, "r2");
});

test("Codex's Interrupt puts its pill back to idle", () => {
hook({ hook_event_name: "UserPromptSubmit", session_id: "s1", coucou_agent: "codex", prompt: "go" });
assert.equal(task("agent_codex").state, "thinking");
Expand Down