Skip to content

feat(devices): show app crashes as a toast in the simulator panel - #20

Open
lauridskern wants to merge 1 commit into
mainfrom
cindara/capture-crashes-in-chat
Open

lauridskern wants to merge 1 commit into
mainfrom
cindara/capture-crashes-in-chat

Conversation

@lauridskern

Copy link
Copy Markdown
Collaborator

What

When an app crashes on an iOS simulator or Android device, the simulator panel now shows a toast with the app name, the crash reason and the stack trace, plus Fix in chat and Attach actions. Before this, a crash just looked like the app disappeared.

Covers the "Catch app crashes and send them to chat" idea from the Mobile Dev Ideas board.

How it works

  • iOS simulator. A crash surfaces in three steps, and the toast updates in place:
    1. SpringBoard logs the exit with its signal, so the toast appears instantly. Normal quits and SIGKILL are ignored.
    2. About a second later, the plugin reads the app's own last log lines (uncaught exceptions, Swift fatal errors, React Native fatal JS errors) and uses them as an early reason and stack.
    3. The symbolicated .ips report from ~/Library/Logs/DiagnosticReports replaces it once ReportCrash writes it, typically 30–45 s later. That delay comes from macOS.
  • Android. A narrow logcat AndroidRuntime:E libc:F *:S stream turns each FATAL EXCEPTION block or libc fatal signal into one crash record.
  • Independent of Logs. Each visible simulator panel opens an app-only mobile_crashes_session that long-polls for up to 20 s and pauses reads while hidden. Log sessions run the same watcher (startCrashWatch), scoped to the app or process they follow, so agents also see crash records via mobile_read_logs, and kind:crash filters them in Logs.
  • Crash records carry a short reason, so the UI never parses messages.

Structure

  • src/server/crash-reports.ts owns all crash detection: pure parsers, the .ips watcher, the SpringBoard exit stream, Android collection, and the startCrashWatch entry point.
  • native-logs.ts changes only by gaining a shared keepReading retry helper, which its existing loop now uses too, and a generic runLogProcess.
  • src/ui/crash-monitor.ts holds the per-panel session and toast state; components/crash-toast.tsx is the view, built from shadcn Buttons and lucide icons.

Telemetry

  • logs.crash.detected counts crashes with device_platform and crash_source (ios_exit, ios_log, ios_report, android_logcat).
  • logs.crash.parse times .ips parsing.
  • UI events: ui.crash.* (toast shown, attached, sent to chat, dismissed, report attached, watch retry).
  • No crash text, stacks, app IDs, paths or device IDs are sent. Documented in docs/telemetry.md.

Limitations

  • Physical iPhones aren't covered, since their crash reports stay on the device.
  • A crash caused by an outside signal, such as kill -ABRT, writes no app log lines, so its stack only arrives with the .ips report.

Testing

  • npm test: 724 passed, 0 failed, 1 skipped. New tests/crash-reports.test.ts covers:
    • .ips, SpringBoard and app-log parsing
    • the report watcher, including partial writes
    • Android collection
    • log session scoping
    • toast state: merge, re-attach, device switching
  • tsc adds no errors over main. npm run typecheck:telemetry passes.
  • Checked the parsers against real Expo Go crashes and simulator logs. The toast design was reviewed in a static preview, light and dark.

Detect app crashes and surface them on the device they happened on, with
actions to attach the crash or ask for a fix in chat.

- iOS simulators report a crash the moment SpringBoard logs the exit,
  add the app's own last log lines about a second later, and replace them
  with the symbolicated .ips report once ReportCrash writes it.
- Android collects FATAL EXCEPTION blocks and libc fatal signals from a
  narrow logcat stream.
- Each visible simulator panel runs an app-only crash session, so the
  toast works without the Logs tool. Log sessions run the same watcher
  scoped to their app or process, and crash rows can be found with
  kind:crash.
- Crash records carry a short `reason` so the UI never re-parses messages.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant