-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Problem
When starting a new session in the desktop app and immediately running a shell command with ! (before sending any plain text message), the app crashes and requires a reload.
Note: The shell command does execute successfully - results are visible when reopening the session after the crash.
Root Cause Analysis
This is a race condition in the shell command submission flow:
- Shell commands are fire-and-forget: In
prompt-input.tsx,sdk.client.session.shell()is called withoutawaitand no optimistic message is added - Navigation triggers sync immediately: After session creation,
navigate(existing.id)triggers a session sync viacreateEffectinsession.tsx - Sync returns empty messages: If the sync runs before the server processes the shell command, it returns no messages
- Part component crashes on undefined:
SessionTurnrenders<Part part={assistantParts()[0]} ... />whereassistantParts()[0]isundefined, causingprops.part.typeaccess to crash
Why regular messages don't crash
Regular prompts use sync.session.addOptimisticMessage() to add a local message immediately, preventing the empty state. Shell commands skip this step.
Acceptance Criteria
- Running
!<command>as the first message in a new session should not crash the app - Shell command results should display immediately after execution (or show a loading state)
- No regression in regular shell command functionality
Implementation Details
Affected Files
| File | Lines | Issue |
|---|---|---|
packages/desktop/src/components/prompt-input.tsx |
781-788 | Shell command sent without await, no optimistic message |
packages/ui/src/components/session-turn.tsx |
427-428 | Accesses assistantParts()[0] without null check |
packages/ui/src/components/message-part.tsx |
313-314 | Accesses props.part.type without guard |
Suggested Fix Options
Option 1: Add null guard in Part component (quick fix)
// packages/ui/src/components/message-part.tsx:313
export function Part(props: MessagePartProps) {
if (!props.part) return null // Add guard
const component = createMemo(() => PART_MAPPING[props.part.type])
// ...
}Option 2: Add conditional rendering in SessionTurn (defensive)
// packages/ui/src/components/session-turn.tsx:427
<Match when={isShellMode() && assistantParts()[0]}>
<Part part={assistantParts()[0]} message={msg()} defaultOpen />
</Match>Option 3: Add optimistic message for shell commands (comprehensive, better UX)
// packages/desktop/src/components/prompt-input.tsx:781
if (isShellMode) {
const messageID = Identifier.ascending("message")
sync.session.addOptimisticMessage({
sessionID: existing.id,
messageID,
parts: [{ type: "shell", command: text, sessionID: existing.id, messageID }],
agent,
model,
})
sdk.client.session.shell({...})
return
}Recommended Approach
Combine Option 1 or 2 (prevents crash) with Option 3 (improves UX with immediate feedback).
Tasks
- Add null check guard in
Partcomponent orSessionTurnshell mode - Add optimistic message for shell commands (similar to regular prompts)
- Test shell commands as first message in new session
- Verify no regression in existing shell command functionality