Skip to content

fix(desktop): name the plugin host exit code in a crash report - #756

Closed
zhangqingkun976 wants to merge 1 commit into
vastsa:mainfrom
zhangqingkun976:fix/plugin-host-crash-report
Closed

zhangqingkun976 wants to merge 1 commit into
vastsa:mainfrom
zhangqingkun976:fix/plugin-host-crash-report

Conversation

@zhangqingkun976

Copy link
Copy Markdown
Contributor

Fixes #747 — or rather, makes the next report of it fixable.

The issue contains one sentence, three times: plugin host process exited: io.github.muzimu217.usage-dashboard. That is all the reporter could say, because the exit code the runtime already had never left the audit record. On Windows, 3221225477 (a 0xC0000005 access violation, i.e. native code in the plugin or its sidecar) and 1 (the plugin's own process.exit or an uncaught throw) are completely different investigations, and this one field is what separates them.

What changes

surface before after
load error (the sentence a user pastes) plugin host process exited: <id> plugin host process exited: <id> (exit code 7; last output: error: fixture service worker died)
failed service state plugin host process exited plugin host process exited (exit code 7)
plugin.crash audit record exitCode exitCode, plus message = the newest output line
plugin log channel data: { exitCode } data: { exitCode, lastOutput? }

The exit code is printed together with its unsigned hex form when it is in the range a Windows process reports for a hard fault, because Electron hands it over as a signed int (-1073741819 and 0xC0000005 are the same value). Nothing is interpreted for the reader.

The plugin's last words

The runtime keeps the last three stdout/stderr lines per plugin host process (each bounded at 400 characters) and quotes the newest one in the crash report. A plugin that dies on a thrown error has usually printed the reason first, and that line is the difference between "the plugin is broken" and "the plugin is broken because X". Every line still reaches the audit thread as plugin.stdio — this only makes sure the last one survives into the error the user can paste.

Scope

No new persistence, no permission, manifest, API or protocol change; the plugin process is already piped through onLog, so nothing new is spawned or buffered. The bounded tail lives on the loaded-plugin record and dies with it.

Validation

  • apps/desktop/test/plugin-services.test.mjs — the die-once fixture now writes fixture service worker died to stderr before process.exit(7), and the test asserts exit code 7 on the service state plus the code and the stderr line on the crash audit record; the fake spawner in the test now implements onLog like the real one does. 16/16 pass.
  • node --test apps/desktop/test/plugin-*.test.mjs — 337/338; the one failure (the project's other folders are not an escape hatch) is pre-existing and fails identically on pristine main.
  • pnpm --filter @pi-desktop/desktop typecheck 0; pnpm lint clean; node scripts/check-architecture.mjs passed; pnpm docs:check (78 EN/zh pairs, 495 pages) exit 0.

E2E gate: NOT RUN. The scenario is written (E2E-PLUGIN-crash-report-names-the-exit-code, both locales, with its traceability rows) but no desktop E2E suite was executed — it needs a packaged candidate and a plugin that faults on demand. Alternative validation: the runtime-level test above forks a real host process, kills it and checks the same four surfaces the scenario names. Remaining risk: the plugin page's own error text is read by no driver, so the rendered string is verified only through the contract test.

Docs

07-plugins/05-plugin-lifecycle.md §3.1 (crash report) and §8 (audit fields), 08-meta/decisions-log.md D607, and the E2E scenario above — all mirrored to docs/zh-CN/….

D607 is numbered after the D604–D606 proposed in my open #721 and #748; I will renumber if those land first.

A report could only ever say `plugin host process exited: <id>`: the runtime
already had the process's exit code but kept it in the audit record, so the
sentence a user can paste carried no diagnosis at all (issue vastsa#747 was exactly
that line, with no logs).

- The exit code now reaches the load error, the `failed` service state, the
  `plugin.crash` audit record and the `plugin` log channel. On Windows a hard
  fault (0xC0000005 and friends, which Electron delivers as a negative signed
  int) and a plugin's own `process.exit(1)` are different bugs; the code is
  printed with its unsigned hex form so both readings agree.
- The plugin's newest output line rides with it. The runtime keeps the last
  three stdout/stderr lines per plugin (each bounded at 400 characters) and
  quotes the newest in the load error and the audit record, because a plugin
  that died on a thrown error usually printed the reason first. Every line still
  lands in the audit thread as `plugin.stdio`.
- Nothing new is persisted and no permission, API or protocol surface changes.

The fixture in `plugin-services.test.mjs` now writes a line to stderr before
exiting, and asserts the exit code on the service state and both the code and
the stderr line on the crash audit record.
@vastsa

vastsa commented Sep 21, 2026

Copy link
Copy Markdown
Owner

该 PR 的原始提交已由修复版 #766(包含行缓冲、退出 flush、统一 Windows 退出码和回归测试)合入 main,关闭此 PR 以避免重复合并旧实现。

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.

[Bug] 插件安装失败

2 participants