Skip to content

feat(engine): write last-panic.log + flush Sentry on panic in the CLI#3871

Merged
louis030195 merged 1 commit into
mainfrom
claude/naughty-hugle-558699
Jun 5, 2026
Merged

feat(engine): write last-panic.log + flush Sentry on panic in the CLI#3871
louis030195 merged 1 commit into
mainfrom
claude/naughty-hugle-558699

Conversation

@louis030195

@louis030195 louis030195 commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

what

The screenpipe CLI/engine binary had no panic hook. Integrators who embed it as a child process (for example inside an Electron wrapper) only saw an exit code when it died, never why. The last-panic.log + Sentry-flush machinery existed only in the Tauri desktop app (apps/screenpipe-app-tauri/src-tauri/src/main.rs).

This ports it to the engine binary, installed on the Record (long-running server) path only. Subcommands return earlier, so they are unaffected.

how it behaves on a panic

flowchart TD
    A["panic on any thread"] --> B{"tokio shutdown-time noise?"}
    B -- yes --> Z["suppress, stderr only"]
    B -- no --> C["capture payload + location + backtrace"]
    C --> D["append to last-panic.log in the resolved data dir (fsync); rotated to .prev on startup"]
    C --> E["print PANIC to stderr"]
    C --> F["Sentry capture_message Fatal + flush 2s (no-op if telemetry off)"]
    D --> G["embedding parent reads last-panic.log after the child exits"]
Loading

what is covered

  • Rust panics, on the main thread and background threads. A panic on a worker thread that previously killed only that task, leaving the process degraded and silent, now leaves a record.
  • Not covered: native crashes (DLL access violation / SIGSEGV), Windows Defender kills, and OOM. Those are not Rust panics, so the hook cannot see them. They need exit-code / signal mapping in the parent and/or a native crash handler (follow-up).

does it conflict with the desktop app?

No. The desktop app runs its engine in-process (ServerCore::start) and has its own panic hook; this hook only runs when the screenpipe binary is executed directly, so they are different processes. The only shared resource would be the last-panic.log file, so the hook writes to the resolved data dir (honors --data-dir) rather than always ~/.screenpipe. An embedder running with its own --data-dir therefore keeps its crash log next to its own screenpipe.log and never touches the app's file. (The app does not read last-panic.log on startup, so even a shared dir would not cause misattribution, only rotation churn.)

what about the SDK?

Out of scope here, on purpose. @screenpipe/sdk is an in-process napi addon over the screenpipe-recorder crate; it never runs this binary's main(), so it gets nothing from this hook and is also not destabilized by it. A library should not install a global std::panic::set_hook; crash handling for the SDK belongs at the napi boundary (translate a Rust panic into a JS error the host handles) and is a separate change in that crate.

attribution

No new env var. The crash record reuses the existing TelemetryContext embedder attribution (SCREENPIPE_EMBEDDER / SCREENPIPE_HOST_APP / SCREENPIPE_CUSTOMER_ID / SCREENPIPE_DEPLOYMENT_ID), so a local crash record is identifiable even with telemetry off. When telemetry is on, the Sentry scope is already tagged with the same context (see the configure_scope block just above the hook), so panic events inherit it.

sample last-panic.log

[2026-06-05 14:30:01.482] PANIC on thread 'tokio-runtime-worker' at crates/screenpipe-engine/src/foo.rs:42:13: called `Option::unwrap()` on a `None` value
screenpipe_customer_id=acme screenpipe_embedder=acme-desktop

Backtrace:
   0: ...

(The attribution line is omitted when no SCREENPIPE_* context vars are set.)

notes / follow-ups

  • Written regardless of telemetry, so analytics-disabled deployments still get a local crash record. For those it is the only crash signal that never leaves the machine.
  • The CLI Sentry sample_rate is 0.1, so last-panic.log is the reliable record while the Sentry copy is best-effort. If we want guaranteed panic delivery to Sentry, bump the rate or special-case fatal events.

test plan

  • cargo check -p screenpipe-engine --bin screenpipe compiles, no new warnings
  • cargo test -p screenpipe-engine --lib crash_log passes (4 tests: append, create-missing-dir, rotate, rotate-no-op). The crash_log helpers take the dir as an argument, so they are agnostic to the data-dir change.
  • rustfmt --check clean on the changed files

🤖 Generated with Claude Code

@louis030195 louis030195 force-pushed the claude/naughty-hugle-558699 branch from 9606fef to 6fdc52a Compare June 5, 2026 21:41
The screenpipe CLI/engine binary had no panic hook: integrators who embed
it as a child process (e.g. inside an Electron wrapper) only saw an exit
code when it died, never why. The last-panic.log + Sentry-flush machinery
existed only in the Tauri desktop app.

Port it to the engine binary on the Record (server) path:
- rotate last-panic.log -> .prev on startup, then append the panic message
  + backtrace (fsync'd) so the embedding parent can read the cause after
  the process exits
- flush Sentry in the hook so a fast-exiting panic isn't dropped
- written regardless of telemetry, so analytics-disabled customers still
  get a local crash record
- written to the resolved data dir (honors --data-dir) so it sits next to
  screenpipe.log and doesn't collide with the desktop app's
  ~/.screenpipe/last-panic.log
- stamp the record with the existing embedder attribution (TelemetryContext:
  SCREENPIPE_EMBEDDER / SCREENPIPE_CUSTOMER_ID / ...) so it's identifiable
  with telemetry off; the Sentry scope already carries the same tags

Scope: the binary/child-process embedding only. Covers Rust panics; native
crashes (DLL access violation, SIGSEGV), Defender kills, and OOM are not
panics (need exit-code mapping / a native handler). The in-process
@screenpipe/sdk (napi addon over screenpipe-recorder) doesn't run main(),
so it's unaffected and needs panic handling at the napi boundary instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@louis030195 louis030195 force-pushed the claude/naughty-hugle-558699 branch from 6fdc52a to 8e76c4f Compare June 5, 2026 21:49
@louis030195 louis030195 merged commit bf7fdf7 into main Jun 5, 2026
21 of 22 checks passed
louis030195 pushed a commit that referenced this pull request Jun 6, 2026
Ships the merged Windows fixes (#3870: sign CLI binary, fix auto-destruct
screenpipe-app.exe false-kill, hard-fail VC++ DLL bundling; #3871:
last-panic.log + Sentry flush on panic) plus the new work-hours schedule
CLI flags (--schedule-enabled / --schedule-rule).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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