app: a termination signal closes the window the normal way, not as a crash (#248)

SIGTERM, SIGINT and SIGHUP ended the process on the spot: no last autosave,
and the crash journal left behind became an unclean_exit report at the next
start. The report on #248's machine ("ended without a clean exit between
commands (last: control ui.capture)") has that exact signature and is
reproduced by kill -TERM after a capture; 500 captures of the solved
crank-mirror (50 app runs), 30 CLI snapshots and 250 MCP headless
screenshots exited cleanly. The first signal now asks the window to close;
a second ends the process with the signal's default action.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-16 00:24:26 -04:00
1 parent 3d38753cad
commit 02b408c499
5 files changed
+170 -2

No files matched your search

+6 -2
View File
@@ -50,8 +50,8 @@ error stream.
- `report.json` (`"format": "fieldcraft-crash/1"`): `kind` (`command_panic`: a command hit an
internal error, caught, the model left unchanged; `recovered_panic`: one caught inside a command;
`panic`: an internal error outside commands; `unclean_exit`: the app ended without a clean exit
— an abort, a stack overflow, a crash in a graphics driver, the process being killed — found at
the next start), `message`, `location`, `thread`, `backtrace`, `app`, `system`, `command`,
— an abort, a stack overflow, a crash in a graphics driver, the process being killed outright
(SIGKILL) — found at the next start), `message`, `location`, `thread`, `backtrace`, `app`, `system`, `command`,
`recent_commands` (up to 32), `last_activity` (the last control request, such as
`control ui.capture`), `base`, `count`, `first_seen`, `last_seen`, `notes`
- `model.fieldcraft`: the model the replay starts from
@@ -78,6 +78,10 @@ diagnostics.bug_report` opens the form, `ui.prompt {"answer": "set", …}` sets
**Check that reporting works:** with `FIELDCRAFT_CRASH_TEST=1`, `diagnostics.crash_test {mode}`
crashes on purpose: `panic`, `recovered` or `abort` (reported at the next start).
A request to terminate is no crash: on Linux and macOS, SIGTERM (`kill PID`), SIGINT (Ctrl-C)
and SIGHUP close the window as **Close** does (unsaved work is autosaved for recovery, the
journal ends, no report). A second signal ends the app at once.
### How it stays cheap and small
The app keeps a journal of the window's top-level commands (`crashes/sessions/*.jsonl`, at most