diff --git a/web/src/components/dialogs/KillDialog.test.tsx b/web/src/components/dialogs/KillDialog.test.tsx
index 5046a27..aed5dd1 100644
--- a/web/src/components/dialogs/KillDialog.test.tsx
+++ b/web/src/components/dialogs/KillDialog.test.tsx
@@ -71,4 +71,33 @@ describe("KillDialog", () => {
await waitFor(() => expect(kill.disabled).toBe(false));
expect(screen.getByText("Failed to kill: daemon unreachable")).not.toBeNull();
});
+
+ it("resets pending when force changes, so the force button is clickable", async () => {
+ // Simulate the Sidebar flow: onConfirm resolves (parent switches to
+ // force mode via rerender), but pending was set to true before the call.
+ const onConfirm = vi.fn(() => Promise.resolve());
+ const { rerender } = render(
+ ,
+ );
+
+ const kill = screen.getByRole("button", { name: "Kill" }) as HTMLButtonElement;
+ fireEvent.click(kill);
+ expect(kill.disabled).toBe(true);
+
+ // Parent re-renders with force=true (the daemon refused the unforced kill).
+ await waitFor(() => expect(onConfirm).toHaveBeenCalledOnce());
+ rerender(
+ ,
+ );
+
+ const forceBtn = screen.getByRole("button", { name: "Force kill" }) as HTMLButtonElement;
+ expect(forceBtn.disabled).toBe(false);
+ });
});
diff --git a/web/src/components/dialogs/KillDialog.tsx b/web/src/components/dialogs/KillDialog.tsx
index 6674480..3494c2d 100644
--- a/web/src/components/dialogs/KillDialog.tsx
+++ b/web/src/components/dialogs/KillDialog.tsx
@@ -34,6 +34,11 @@ export function KillDialog(props: KillDialogProps) {
// asked for the kill, so Enter confirms it.
useEffect(() => confirmRef.current?.focus(), []);
+ // When the dialog transitions from normal → force mode, the previous
+ // onConfirm resolved without error (it mutated parent state and returned),
+ // so `pending` is still true. Reset it so the force button is clickable.
+ useEffect(() => setPending(false), [force]);
+
const handleConfirm = () => {
setPending(true);
setError(null);