Skip to content

Desktop app crashes when running shell command as first message in new session #191

@shuv1337

Description

@shuv1337

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:

  1. Shell commands are fire-and-forget: In prompt-input.tsx, sdk.client.session.shell() is called without await and no optimistic message is added
  2. Navigation triggers sync immediately: After session creation, navigate(existing.id) triggers a session sync via createEffect in session.tsx
  3. Sync returns empty messages: If the sync runs before the server processes the shell command, it returns no messages
  4. Part component crashes on undefined: SessionTurn renders <Part part={assistantParts()[0]} ... /> where assistantParts()[0] is undefined, causing props.part.type access 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 Part component or SessionTurn shell 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions