Skip to content

Persist the guest console instead of discarding it - #166

Merged
themartiano merged 4 commits into
omacom:mainfrom
jtavin-smp:fix/persist-guest-console-log
Sep 9, 2026
Merged

Persist the guest console instead of discarding it#166
themartiano merged 4 commits into
omacom:mainfrom
jtavin-smp:fix/persist-guest-console-log

Conversation

@jtavin-smp

Copy link
Copy Markdown
Contributor

Fixes #165.

Problem

The guest console is bound to a stdio chardev (macos/run-qemu-gpu.sh:1424), and the helper hands QEMU its own stdout (QEMUGPULauncher.swift:759). For an app launched from Finder that is /dev/null, so every console byte is discarded on every normal launch. stderr is piped and read by the helper, but the console is not on stderr.

Confirmed on a running VM:

$ lsof -p <qemu pid> | awk '$4 ~ /^[012][a-z]*$/ {print $4, $5, $9}'
0r CHR /dev/null
1u CHR /dev/null
2  PIPE

The practical cost is that a guest which fails to boot, wedges its network, or hangs leaves no host-side record, and a restart destroys what was there.

Change

Give the existing chardev an explicit logfile=, pointed at the VM's own state directory. Nothing about how the console is attached changes.

  • Persistent launches log to <VM root>/console.log, next to the VM they describe, so the log survives app updates exactly as the rest of that directory does.
  • One generation is rotated aside to console.log.1 at launch. This is the part that makes the feature useful: a fault bad enough to force a reboot is only readable after that reboot has already truncated the live log.
  • Ephemeral launches log into the temporary workspace and discard it with the rest of that state.
  • The option value is comma-escaped, since QEMU splits chardev options on commas and a user-selected VM location may contain one.

logappend=off plus the rotation keeps this bounded at two files rather than growing without limit.

Verification

End to end with the bundled runtime, booting the bundled kernel with no root disk:

$ ls -l cons2.log
-rw-r--r--  1 user  wheel  92610 cons2.log
$ head -3 cons2.log
[    0.179835] usbcore: registered new interface driver uas
[    0.179925] usbcore: registered new interface driver usb-storage
[    0.180001] usbcore: registered new interface driver ums-alauda

92,610 bytes captured where nothing is captured today.

macos/Tests/run-qemu-ssh-contract.test.sh passes, with two added assertions: the chardev carries the logfile option, and a pre-existing console.log is rotated to console.log.1 with its contents intact.

The test's fake storage shim previously set QEMU_PERSISTENT_STORAGE_DIRECTORY but not QEMU_PERSISTENT_STORAGE_ROOT, even though its own -kernel "$persistent_root/boot/kernel" assertion treats FAKE_PERSISTENT_ROOT as the root. This adds the missing assignment so the shim reflects what the real module exports.

Note on make test

make test does not currently complete in my environment: swift test fails with no such module 'Testing' (48 occurrences). This is not caused by this change — it reproduces identically on a clean main worktree at 768d03d with no modifications, and this PR touches no Swift sources. Every shell and Python test in the suite that runs before it passes. Flagging it rather than claiming a green run.

Docs

README gains a short "Guest console log" section under Data and updates, covering both paths, the rotation, and a note that the log contains guest output and is worth reviewing before attaching it to a bug report.

The guest console is bound to a stdio chardev and QEMU inherits the
helper app's stdout, which is /dev/null for a Finder launch, so every
console byte is discarded on every normal launch. A guest that fails to
boot, wedges its network, or hangs leaves no host-side record, and a
restart destroys whatever was there.

Give the chardev an explicit logfile under the VM state directory and
rotate one generation aside at launch, since a fault serious enough to
force a reboot is only readable after that reboot has replaced the live
log. Ephemeral launches log into the temporary workspace. The option
value is comma-escaped because QEMU splits chardev options on commas.

Verified with the bundled runtime booting the bundled kernel with no
root disk: 92,610 bytes of console output captured where nothing is
captured today.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jtavin-smp
jtavin-smp force-pushed the fix/persist-guest-console-log branch from 93bf6fe to d5c430d Compare September 8, 2026 13:14
@themartiano

Copy link
Copy Markdown
Collaborator

Merged main, fixed some CI issues and merging.
Thank you!

@themartiano
themartiano merged commit 2b1ff1b into omacom:main Sep 9, 2026
1 check passed
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.

Guest console output is discarded: stdio chardev inherits the app's /dev/null stdout

2 participants