Skip to content

fix: say why the window died, in the log and the dialog - #589

Merged
omartelo merged 1 commit into
mainfrom
fix/window-stderr-in-log
Sep 11, 2026
Merged

omartelo merged 1 commit into
mainfrom
fix/window-stderr-in-log

Conversation

@omartelo

@omartelo omartelo commented Sep 11, 2026 •

Copy link
Copy Markdown
Collaborator

A lich 0.49.0 on Ubuntu opened no window, and lich.log held only chromium shell err="signal: trace/breakpoint trap (core dumped)", then the same signal for focus existing window. The reason was one Chromium FATAL on the window's stderr (the sandbox helper under cef/, fixed in #587), and launch handed that stream straight to lich's own stderr, which a launcher start does not have. lich rage bundles the log, so it had nothing either.

What changes

launch is where every window starts: Run, Focus, the macOS tab fallback inside startupGrace, and Windows, which has no launch path of its own. So the fix is there and covers all four. The window's stderr now goes through stderrTail, which passes every byte on to lich's stderr as before and keeps the end of it. A window that exits with an error comes back as chromium.ExitError, whose message is the exit followed by the tail, so each existing log site (chromium shell, focus existing window, bundled window died at startup, opening a tab instead) records it without a change at the call site. The dialog in main.go shows chromium.Why(err): the exit and only the lines that say why.

Measured against the installed 0.49.0 lich-shell, headless (--ozone-platform=headless), with --disable-namespace-sandbox to reach the same helper check:

  • the crash writes exactly one line, [..:FATAL:sandbox/linux/suid/client/setuid_sandbox_host.cc:166] The SUID sandbox helper binary was found, but is not configured correctly..., and exits on SIGTRAP with a core dump
  • a healthy headless start writes one Fontconfig warning
  • the stderr pipe reached EOF within 10 ms of the process exit, clean or FATAL

Bounds, and why FATAL lines win the space

Nothing is logged while the window runs: lich.log rotates at 5 MiB and a healthy window is chatty. A failed exit logs its last 20 lines, each cut at 1 KiB. The first three FATAL or Check failed lines are kept ahead of the tail once they scroll out of it, with ... wherever lines around them were dropped. Chromium aborts on its first FATAL, so that line is the cause, and what follows it is consequence: subprocesses losing the browser and saying so, a crash dump, a GPU process restarting. In the crash measured here the FATAL line was the last one, so a plain tail would have caught it; the rule is for the crash whose cause is followed by more than 20 lines of fallout. First ones rather than last, because later FATALs are the cascade.

The dialog gets those FATAL lines, at most three, or the last three lines when there are none. The whole tail in a dialog reads as a wall of warnings.

The pipe

os.Stderr was a file the child inherited. A writer that is not a file makes exec copy through a pipe, and Wait then returns only once every holder of the write end has closed it, the window's subprocesses included. WaitDelay (2 s) bounds that after the window exits, and the ErrWaitDelay it reports, only ever after a clean exit, maps back to nil: the window closed as asked. The mirror swallows its own write errors for the same reason, since exec reports a copy error after a clean exit as the command failing, and the windowsgui build has no stderr to write to.

Not covered

  • Windows takes the same path, unmeasured: whether Chromium writes its FATAL lines to stderr there, rather than only to its own debug log, has not been seen, so the tail may carry only what lich-shell prints itself. Written down in docs/ceilings.md, with lich -- --enable-logging=stderr as the switch that asks for them.
  • A window that misbehaves and keeps running leaves nothing in the log, by design.

Test plan

  • gofmt -l . and go vet ./... clean
  • go test ./..., plus -race on internal/chromium (package coverage 92.7%, stderr.go 100%)
  • Cross-compile loop for linux, darwin and windows
  • New: the tail mirrors every byte and never fails the copy on a missing terminal; keeps the last 20 lines; keeps the first three FATAL lines that scroll out, with gaps; cuts a line at 1 KiB; a clean exit and a drain overrun are nil and a failed exit is an ExitError wrapping it; Why picks FATAL lines over the noise, caps them at three and falls back to the last three lines; Focus on a stand-in window that writes a FATAL line and exits 5 returns that line
  • Mutants of tailLines, lineBytes and whyLines, one either side, each fail the suite
  • Reproduced the reported crash headless against the installed lich-shell and read the stderr and pipe timings above
  • Rebased onto main after fix: set the sandbox helper Chromium actually reads setuid #587 merged; kept both CHANGELOG Fixed entries, gate rerun green
  • On a desk: launch a broken install from the app menu and read the dialog and lich.log

The window's stderr went straight to lich's own, which a launcher start
does not have, so a crash left only its signal in lich.log. launch now
keeps a bounded tail of it and returns it on a failed exit: every log
site records it, and the error dialog shows the FATAL lines.
@omartelo
omartelo force-pushed the fix/window-stderr-in-log branch from 887b5eb to 9069001 Compare September 11, 2026 18:14
@omartelo
omartelo merged commit 0abf095 into main Sep 11, 2026
7 checks passed
@omartelo
omartelo deleted the fix/window-stderr-in-log branch September 11, 2026 18:32
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