Skip to content

Commit cdba186

Browse files
claudekhaliqgant
authored andcommitted
fix: guard cliAvailability setState against unmount
Both SpawnAgentDialog and TerminalPane lacked a cancelled flag in the CLI availability useEffect, allowing setCliAvailability to fire on an unmounted component if the user closed the pane before the IPC round trip completed. Pattern matches the existing loadPersonas/loadBurnSummaries guards in the same files. https://claude.ai/code/session_01KXU1uAUwx3L82TMLnAmU4z
1 parent e0cde1b commit cdba186

3 files changed

Lines changed: 31 additions & 2 deletions

File tree

check_types.mjs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Check if Object.fromEntries result is assignable to Partial<Record<SpawnAgentCli, boolean>>
2+
3+
// According to TypeScript:
4+
// Partial<Record<SpawnAgentCli, boolean>> expands to:
5+
// { claude?: boolean; codex?: boolean; opencode?: boolean }
6+
7+
// Object.fromEntries returns: { [k: string]: any }
8+
// In the context of:
9+
// const results: Array<[SpawnAgentCli, boolean]> = [['claude', true], ...]
10+
// Object.fromEntries(results) is typed as: { [k: string]: boolean }
11+
12+
// The question: is { [k: string]: boolean } assignable to
13+
// { claude?: boolean; codex?: boolean; opencode?: boolean }?
14+
15+
// Answer: YES, because:
16+
// 1. { [k: string]: boolean } is an index signature that matches ANY string key
17+
// 2. { claude?: boolean; codex?: boolean; opencode?: boolean } only uses specific keys
18+
// 3. Any value with an index signature { [k: string]: T } satisfies a type with specific optional properties
19+
20+
console.log("Type check analysis:");
21+
console.log("Object.fromEntries(results) type: { [k: string]: boolean }");
22+
console.log("setCliAvailability expects: Partial<Record<SpawnAgentCli, boolean>>");
23+
console.log("Which expands to: { claude?: boolean; codex?: boolean; opencode?: boolean }");
24+
console.log("");
25+
console.log("TypeScript assignability: YES - index signature types are assignable to property-specific types");

src/renderer/src/components/sidebar/SpawnAgentDialog.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,15 @@ export function SpawnAgentDialog(): React.ReactNode {
4949
}, [project, root?.id, selectedRootId])
5050

5151
useEffect(() => {
52+
let cancelled = false
5253
const clis: SpawnAgentCli[] = ['claude', 'codex', 'opencode']
5354
void Promise.all(clis.map(async (cli) => {
5455
const available = await pear.broker.checkCliAvailable(cli).catch(() => false)
5556
return [cli, available] as const
5657
})).then((results) => {
57-
setCliAvailability(Object.fromEntries(results))
58+
if (!cancelled) setCliAvailability(Object.fromEntries(results))
5859
})
60+
return () => { cancelled = true }
5961
}, [])
6062

6163
useEffect(() => {

src/renderer/src/components/terminal/TerminalPane.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -565,13 +565,15 @@ export function TerminalPane(): React.ReactNode {
565565
}
566566

567567
useEffect(() => {
568+
let cancelled = false
568569
const clis: SpawnAgentCli[] = ['claude', 'codex', 'grok', 'opencode']
569570
void Promise.all(clis.map(async (cli) => {
570571
const available = await pear.broker.checkCliAvailable(cli).catch(() => false)
571572
return [cli, available] as const
572573
})).then((results) => {
573-
setCliAvailability(Object.fromEntries(results))
574+
if (!cancelled) setCliAvailability(Object.fromEntries(results))
574575
})
576+
return () => { cancelled = true }
575577
}, [])
576578

577579
useEffect(() => {

0 commit comments

Comments
 (0)