Skip to content

Repair CI, the local console, and process resource ownership - #21

Merged
Pummelchen merged 16 commits into
mainfrom
fix/ci-compile-check-and-abi-contract
Aug 23, 2026
Merged

Pummelchen merged 16 commits into
mainfrom
fix/ci-compile-check-and-abi-contract

Conversation

@Pummelchen

@Pummelchen Pummelchen commented Aug 22, 2026 •

Copy link
Copy Markdown
Owner

Sixteen commits. main had five failing CI jobs when this branch started; it now has one, and that one is pre-existing and untouched here.

CI repairs

  • Compile Check — compile-check gained a libc prerequisite in 47ff4f1 but the job was never updated. It needs llvm-ar, meson, ninja and the picolibc submodule; it had clang lld and no submodules.
  • ABI Contract — XAIOS_CAP_CREDENTIAL_READ shipped in syscall.h but was never recorded in the frozen contract. Records a capability the kernel already enforces; widens nothing.
  • Hosted Engine & Model v2 — make hosted-test could not build on Linux. BearSSL getentropy() is hidden by glibc under -std=c99, and openbsd-compat bcrypt_pbkdf.c uses __nonstring__, unknown to clang before 21. macOS hid both.

Local console

Three defects, all invisible to the existing gates because every interoperability suite drives XAIOS over SSH.

  • Output printed twice. klog_console_write wrote each byte to the UART and into the capture buffer the shell then replayed. On the local console the UART is the terminal. Also stops publishing remote SSH command output on the physical serial port.
  • xaiosctl was unreachable. help advertised it and sysinfo/status pointed users at it, but it had no dispatch entry — and it was int main(void), a self-test harness, so registering it alone would have run the tests instead of the operation. It now takes arguments and forwards them to the control protocol under the observer role.
  • Six-digit console PIN (012345), console-only, never accepted over SSH, and gated behind password auth so a key-only image stays key-only. Five failures now lock the prompt for 60s — g_console_auth_failures was previously counted and never acted on, so neither the PIN nor the password had any throttle.

VMware Fusion

The Fusion window could be watched but not used: the framebuffer was a status panel that never echoed input or rendered output, and the serial port was write-only.

  • Framebuffer terminal — after boot the display mirrors the console byte stream, with CSI parsing, wrapping and scrolling. Verified by reading the rendered pixels back on the real device.
  • USB keyboard — two bugs. The control-endpoint ring was never rewound between devices, so everything past the first was unreachable; and the port-5 device is a composite HID whose interfaces are both subclass 0 / protocol 0, so the report descriptor is now used to identify a keyboard rather than binding a pointing device by mistake.
  • XAIOS_FUSION_SERIAL=pipe for a bidirectional serial console.

Storage and process ownership

  • Snapshot state could never persist. persistence wrote to the default block device — the test image the launcher attaches with snapshot=on, so every write was discarded. PERSISTENCE_SECTOR (3000) sits just below MFS_START_SECTOR (3072); only the device binding was wrong.
  • MutableFS had no locking at all while reachable from four vCPUs, on a kernel with no global syscall lock. Every entry point is now serialised.
  • mutable_fs_write_fd cleared a 256 KiB buffer on every call — ~21 MiB of memset to write 3 KiB of audit log.
  • Process resources were keyed on a recycled pid. One run issued and reaped pid 32 nineteen times, and every SSH child channel ran as child=32. VFS handles and sockets keyed on it, so any transient exiting released a live session's file handle. This was the FreeBSD gate's intermittent failure.

FreeBSD gate

Two separate problems, both now fixed:

  • The outbound budget was keyed on guest architecture (600s x86_64, 60s aarch64), which only holds on an ARM host. On an x86_64 runner the cross-emulated aarch64 guest got 60s. Now derived from the host/guest pairing; nothing that passes today gets less time.
  • The gate produced no guest-side evidence — non-verbose builds silence klog after boot. It now captures kernel diagnostics, which is what named the ownership bug on the first failure after landing.

The gate now passes in CI, for the first time.

Status

15 of 16 checks pass. Core OS Aggregate RC fails with exactly the same five items as main — smmuv3 exited 2, nvme exited 2, and three missing markers — verified unchanged across every commit on this branch.

Not addressed: those five items, and sustained parser fuzzing on the SSH stack and DNSSEC validator.

🤖 Generated with Claude Code

André Borchert and others added 3 commits August 22, 2026 23:56
XAIOS_CAP_CREDENTIAL_READ has shipped in kernel/include/xaios/syscall.h
since the DNS/SSH interoperability work, and gates fs_open on
/etc/xaios_ssh_client_identity plus the async remote-login child grant.
It was never added to the frozen release-candidate contract, so
validate_syscall_abi reported "contract missing source capabilities"
and make qemu-abi-contract failed on main.

Add the bit to the contract capability list so it matches source.

Verified: python3 tests/scripts/qemu-abi-contract.py reports status=pass
with all eight checks green, on macOS and on the Debian 13 x86_64 box.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
compile-check gained a libc prerequisite in 47ff4f1, but the CI job was
never updated for it. scripts/build-libc.sh requires llvm-ar, meson and
ninja, and picolibc must be present, so the job failed on main with
"required tool not found: llvm-ar". The checkout also omitted submodules,
which would have failed next with a missing Picolibc submodule.

Install llvm, meson, ninja-build and python3 alongside clang and lld, and
check out submodules recursively, matching the hosted-c99-libc job that
already builds the same target successfully.

Verified: make compile-check exits 0 with "All freestanding C files
compiled clean" on Debian 13 x86_64 with exactly this tool set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
make hosted-test could not build on Linux, so the Hosted Engine & Model v2
job has failed on main for at least six consecutive runs. Two independent
causes, both in host-only compiles of vendored third-party code:

BearSSL's sysrng.c calls getentropy(), which glibc declares in <unistd.h>
only under _DEFAULT_SOURCE. HOST_CFLAGS uses -std=c99, which sets
__STRICT_ANSI__ and hides the declaration, so the call became an implicit
declaration and -Werror rejected it. macOS declares it regardless, which
is why this only ever failed on Linux. Define _DEFAULT_SOURCE for that
compile.

openbsd-compat's bcrypt_pbkdf.c uses __attribute__((__nonstring__)),
which clang did not recognize before version 21, so -Wunknown-attributes
fired under -Werror. scripts/build-image.sh already compiles these same
two vendored files with -Wno-unknown-attributes; apply the same flag to
the hosted test compile rather than diverging from upstream source.

Verified: make hosted-test now runs to completion and exits 0 on Debian 13
x86_64 with clang 19, ending at the model-volume reader tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Pummelchen Pummelchen changed the title Fix the Compile Check and ABI Contract CI failures Fix three long-standing CI failures Aug 22, 2026
The bidirectional suite gave the outbound SSH conversation 600 seconds for
an x86_64 guest and 60 for an aarch64 guest. That mapping only holds on an
AArch64 development host, where aarch64 is native and x86_64 is emulated.
On an x86_64 CI runner it inverts: the aarch64 guest is the cross-emulated,
order-of-magnitude-slower one, and it is the one handed 60 seconds. The job
has failed on main with "timed out waiting for freebsd-outbound-ssh-ok"
ever since, while the x86_64 variant passes on an x86_64 host.

Derive the default from the host/guest pairing instead. Cross-architecture
emulation gets the large budget; native pairings keep today's values, so no
configuration that currently passes is given less time:

  arm64  host + aarch64 guest ->  60s  (unchanged, verified below)
  arm64  host + x86_64  guest -> 600s  (unchanged)
  x86_64 host + aarch64 guest -> 600s  (was 60s; the CI failure)
  x86_64 host + x86_64  guest -> 600s  (unchanged)

An unrecognized host architecture falls back to the large budget.
XAIOS_OUTBOUND_TIMEOUT still overrides everything.

Verified: the aarch64 suite passes end to end on an Apple Silicon host with
this change, 19 of 19 checks, including the outbound SSH, SCP recursive
upload/download, ProxyJump and agent-forwarding legs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Pummelchen Pummelchen changed the title Fix three long-standing CI failures Fix four long-standing CI failures Aug 22, 2026
@Pummelchen Pummelchen changed the title Fix four long-standing CI failures Fix three CI failures, and unblock the FreeBSD gate's first timeout Aug 22, 2026
André Borchert and others added 12 commits August 23, 2026 09:11
klog_console_write copied each byte into the active capture buffer and also
wrote it to the UART. The shell then appended that same captured buffer to
the command's output, so on the local console -- where the UART is the
user's terminal -- every transient application printed its output twice:

  admin@xaios:/$ df
  Filesystem Size Used Avail Capacity Mounted on
  MutableFS 2048K 62K 1986K 4% /
  Filesystem Size Used Avail Capacity Mounted on   <-- repeated
  MutableFS 2048K 62K 1986K 4% /

Over SSH the UART is not the client's terminal, so the duplicate was
invisible to every interoperability gate, all of which drive XAIOS over SSH.

Output produced while a session is capturing belongs to that session: the
capturing caller relays it to its own terminal exactly once. Skip the UART
echo while a capture is active.

This also stops publishing the output of remote SSH commands on the
machine's physical serial port.

Boot is unaffected: no capture is active during startup self-tests.

Verified: make qemu-smoke and qemu-local-console-gate pass, and ls, ps, df,
stat and cat each print once on AArch64 and x86_64.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The shell's help text listed xaiosctl, and both sysinfo and status told the
user to "use xaiosctl status", but the command was not in the shell's app
table at all, so it always failed:

  admin@xaios:/$ xaiosctl status
  xaios: xaiosctl: command not found

/bin/xaiosctl was also not a CLI. It was declared int main(void) and ran a
fixed control-protocol self-test suite, so simply registering it would have
run the test harness rather than the requested operation.

Give it an argument path: with arguments it forwards the command line to the
control protocol and prints the rendered response; without arguments it stays
the existing self-test, so boot and gate evidence is unchanged. Output goes
to the console rather than the kernel log, because the shell relays captured
console bytes verbatim while log lines are filtered to this application's
path prefix.

Commands run under the observer role that xaios_control_run already defaults
to, so read-only operations succeed and privileged ones are refused by the
control plane. The shell grants no admin capability.

Verified on AArch64 and x86_64: xaiosctl status and xaiosctl version return
real output, and the boot self-test markers are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three findings from a clang --analyze pass, none of which alters behaviour:

ssh_mlkem768_self_test computed its XOR difference over sender_secret and
receiver_secret unconditionally, including when keypair or encapsulation had
failed and neither buffer was written. The result was correctly discarded by
the result == 0 guard, so the outcome was right, but reading uninitialised
stack is undefined and would trip MemorySanitizer the moment it is enabled.
Compare only on the success path.

mutable_fs open() re-read the node after a truncate into a variable nothing
subsequently used. Removed; the CREATE branch's lookup above it is still
live, feeding the truncate condition.

git_workspace diff computed a line cap from XAIOS_GIT_WORKSPACE_DIFF_MAX_LINES
and never read it, so the limit has never applied -- the loop runs on the
unclamped counts and the emitted diff is bounded by hunk_capacity instead.
The dead computation is removed rather than switched on: the cap is 32 lines,
so enforcing it now would begin truncating any diff of a longer file, and
that is a behaviour change that wants its own decision.

Verified: make compile-check, docs-check, qemu-abi-contract, qemu-smoke and
qemu-local-console-gate all pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The local console now accepts a six-digit PIN at the login prompt in addition
to admin/password. Six digits authenticate directly; any other entry is still
treated as a user name. The default development PIN is 012345.

A 10^6 search space is small, so the credential is deliberately fenced in:

  - Console only. The PIN is never offered or accepted over SSH, where a
    network attacker could search that space quickly.
  - It rides along with password authentication rather than widening the
    authentication surface: load_console_pin returns early when password auth
    is disabled, the build refuses to package a PIN without a user database,
    and release builds refuse one outright. A key-only image stays key-only.
  - Five consecutive console failures now lock the prompt for 60 seconds, and
    a correct credential is refused while the lockout stands. This applies to
    password and PIN attempts alike; g_console_auth_failures was previously
    counted and never acted on, so neither path was throttled at all.

Stored as PBKDF2-SHA256 with its own salt in /etc/xaios_console_pin, verified
with the same constant-time compare as the password, with availability folded
into the accumulator so a missing record cannot authenticate. The kernel
provisions the record read-only like the user database. Digits are masked as
they are typed, since a PIN is a secret rather than a user name.

Verified on AArch64 and x86_64: correct PIN opens a shell, wrong PIN is
rejected, input is masked, username/password still works, the lockout trips
on the fifth failure and refuses the correct PIN while it stands. docs-check
and qemu-local-console-gate pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The generated Fusion profile wires serial0 to a file, so the guest can print
but nothing can be typed back, and the Fusion window itself only shows the
boot status screen: progress bar, address, SSH state and a XAIOS LOGIN prompt
with a cursor. That screen reflects console state and never echoes input or
renders command output, so there was no way to operate the guest from the
console at all -- only over SSH.

Add XAIOS_FUSION_SERIAL=pipe, which configures serial0 as a VMware pipe
endpoint. VMware then creates a UNIX socket that a terminal can attach to,
giving a real interactive console:

  XAIOS_FUSION_SERIAL=pipe ./scripts/build-vmware-fusion.sh
  ./scripts/run-vmware-fusion.sh
  nc -U /tmp/xaios-fusion-console

XAIOS_FUSION_SERIAL_PIPE moves the socket. The default stays "file" so
vmware-fusion-smoke keeps reading fusion-serial.log unchanged.

Verified on Fusion 26.0.0: attaching to the pipe reaches the login prompt,
the six-digit PIN opens a shell, and echo, df and xaiosctl version all run
and return output. The default profile still passes vmware-fusion-smoke.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Typing in the Fusion window did nothing because no keyboard was ever bound.
Two independent causes, found by dumping what the guest actually enumerates.

The control endpoint ring was never rewound between devices. Each slot's
device context starts its control endpoint at the base of the shared ep0
ring, but the driver kept its enqueue position from the previous device, so
every control transfer to the second and later devices timed out waiting for
completions referencing TRBs the controller had already passed. On QEMU the
keyboard is the first device enumerated, so this never showed. On Fusion the
keyboard is on port 6, behind a device on port 5, and was unreachable:

  input: xHCI control transfer timed out
  input: xHCI GET_DEVICE_DESCRIPTOR failed
  input: xHCI HID configuration failed port=6 slot=2

Reset the index and clear the ring before addressing each device.

The port 5 device is also HID, but a composite whose interfaces are both
subclass 0 / protocol 0, so the boot descriptors cannot identify it. Both
interfaces carry an 8-byte interrupt IN endpoint, so accepting the first HID
interface with a plausible report size would have bound a pointing device as
a keyboard and typed noise. Fall back to the report descriptor instead and
require a Generic Desktop Keyboard usage; both port 5 interfaces declare
Usage Mouse (05 01 09 02) and are correctly refused.

The failure path now logs the interfaces and endpoints a rejected device
advertises, because a controller that enumerates but exposes no keyboard is
a descriptor question and the descriptor is the only thing that answers it.

Verified: Fusion 26.0.0 reports "xHCI HID boot keyboard initialized" and
passes the translation self-test; qemu-keyboard-input-gate still passes on
AArch64.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The display was a status panel: it drew the progress bar, address, SSH state
and a XAIOS LOGIN prompt with a cursor, but it reflected console state rather
than showing the console. It never echoed a keystroke or rendered command
output, so a machine whose only console is the screen -- VMware Fusion, which
wires its serial port to a file -- could be watched but not used.

Add a framebuffer terminal and hand the display over to it once boot
completes. From that point the framebuffer receives exactly the byte stream
the UART receives, so the screen shows the same session a QEMU serial console
shows: prompt, echoed input, command output, scrollback.

The terminal handles newline, carriage return, backspace and tab, wraps and
scrolls, and parses CSI sequences so the shell's colour codes render rather
than printing as noise. Bytes belonging to a capturing session are excluded,
exactly as they are for the UART, so output still appears once.

boot_ui_self_test renders a glyph and reads the pixels back, so the display
path is proven on the machine that has a framebuffer instead of assumed.

Known limitation: the bundled font covers ' ' through '_' and folds lowercase
to uppercase, so the terminal renders uppercase. Extending the font is a
separate change.

Verified on Fusion 26.0.0: "framebuffer 1024x768 glyph readback lit=56
passed" and "framebuffer terminal active 112x41 cells". QEMU has no
framebuffer, so the terminal stays dormant there and qemu-local-console-gate,
qemu-keyboard-input-gate, docs-check and qemu-abi-contract all still pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The gate has failed twice in CI for two different reasons -- ProxyJump host
key verification once, an SFTP write the next run -- and neither failure left
any guest-side evidence. The uploaded serial log stopped at the login prompt,
because a non-verbose build calls klog_console_set_log_output(0) once boot
finishes, so everything the kernel had to say about the failure was discarded.

The information already exists. MutableFS reports exactly why a write fails:

  mutable-fs: write allocation failed path=%s blocks=%u used=%lu
  mutable-fs: write block IO failed path=%s blocks=%u
  mutable-fs: write rejected path=%s mounted=%u flags=0x%x

None of it reached the artifact. Build the guest with XAIOS_BOOT_VERBOSE so
kernel logging stays on for the whole run and lands in the evidence that is
already uploaded. XAIOS_BOOT_VERBOSE is honoured if the caller sets it.

Both observed failures are the same underlying event: a failed MutableFS
write. The SFTP failure is a pwrite returning <= 0, reported as SSH_FX_FAILURE
"Write failed". The ProxyJump failure is verify_known_host in ssh_client.c
returning -1 after its pwrite or fsync fails, which surfaces to the user as
"host key verification failed". Naming the cause needs the kernel's own
message, which this change preserves.

Verified: the suite passes locally and the captured log grows from boot output
only to 715 lines of kernel diagnostics, including every filesystem operation
with path, size, block count and generation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mutable_fs_write_fd cleared its entire staging buffer before every write. The
buffer is sized for the largest supported file, MFS_V5_MAX_FILE_BYTES, so
appending one audit line meant a 256 KiB memset. The SSH audit log alone paid
roughly 21 MiB of it to write 3 KiB of lines across one interoperability run,
and every fd writer -- SFTP uploads, xapt, editor saves -- paid the same toll
per call. Under TCG emulation that is not free.

Only a cursor seeked past the end of the file leaves a hole that has to read
back as zeros, so clear just that hole. Everything below file_size was filled
by the read_file above, everything from the cursor on is overwritten by the
caller's data, and nothing above new_size is written to the volume, so stale
bytes further up the buffer cannot reach the disk or leak into a file.

This does not change the read-modify-write shape of the call. Rewriting the
whole file on each append is inherent to the volume's copy-on-write update,
which allocates a fresh block set before releasing the old one; making appends
touch only the affected blocks means sharing unchanged blocks between
generations, which is a separate change with crash-consistency consequences.

Verified: qemu-smoke, qemu-milestone-gate 62 (filesystem), qemu-storage-crash
(all metadata kill points recovered), qemu-local-console-gate and
compile-check all pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Snapshot state never survived a restart. persistence_save_to_disk called the
default block device, which is vblk0 -- the initramfs/test image that
run-qemu-aarch64.sh attaches with snapshot=on, so every write to it is
discarded when the machine stops. The first boot wrote its five records and
read them straight back from the same overlay, which looked like success; the
next boot found an empty sector and logged "no existing disk state".

The layout already anticipated the right home for it: PERSISTENCE_SECTOR is
3000 and MFS_START_SECTOR is 3072, so the snapshot sector was meant to sit
just below MutableFS on the same durable volume. Only the device binding was
wrong, and both candidate devices are larger than sector 3000, so the capacity
assertion passed either way and nothing complained.

Add persistence_bind_block_device and route the sector reads and writes
through it. kmain now opens the dedicated persistent slot before the
persistence self-test and reuses that handle for the MutableFS mount below
rather than opening it twice. When no durable slot exists the default device
is still used, which keeps the pre-storage self-test working on machines that
expose no writable volume.

Verified: qemu-persistence-reboot now reports "mutable VirtIO state survived
QEMU reboot" with persistence_boot_loads=1, where it previously failed the
second boot. qemu-smoke, filesystem milestone 62, qemu-storage-crash and
qemu-local-console-gate all pass.

Note this gate is not part of the CI workflow, which is why a subsystem that
could never persist went unnoticed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MutableFS had no mutual exclusion of any kind. Its node table, open-file
table and block bitmap were mutated without a lock while being reachable from
every CPU through the filesystem syscalls and from kernel services, on a
kernel that runs four vCPUs in the interoperability gates and has no global
syscall lock -- only a socket-table lock. Every neighbouring subsystem locks:
network_stack, klog, scheduler and kheap all take spinlocks. This one did not.

That is the most plausible explanation for the FreeBSD gate's two different
intermittent failures, which are the same underlying event: a MutableFS write
returning an error. One run failed an SFTP write, reported as SSH_FX_FAILURE
"Write failed"; the next failed the known_hosts write inside
verify_known_host, which surfaces as "host key verification failed". Both are
timing dependent, both only appear under slow emulation where the interleaving
differs, and both pass on a fast native host.

Each public entry point becomes a thin wrapper that takes one lock around the
existing body, now named *_locked. mutable_fs_mount_persistent calls
mutable_fs_mount_device_locked so the pair stays inside a single acquisition.
mutable_fs_self_test is deliberately left unwrapped: it drives these same
entry points and runs single threaded during boot. The read-only counter
accessors are left alone; they return a single word.

Re-entrancy was checked rather than assumed. MutableFS calls only
block_device_*, virtio_block_*, klog and kassert, none of which re-enter it.
klog_ring_write is purely in-memory; the klog_ring functions that do touch
MutableFS are klog_ring_init, klog_flush and klog_rotate, and klog_flush is
reached from klog_level only at panic level, which MutableFS never emits.

This does not prove the FreeBSD flakiness is gone -- that needs repeated CI
runs, and the gate now captures the kernel's own diagnostics if it recurs.

Verified: qemu-smoke, filesystem milestone 62 (three consecutive runs),
qemu-storage-crash with all metadata kill points recovered,
qemu-local-console-gate, qemu-persistence-reboot and compile-check all pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The FreeBSD gate's intermittent failures were file handles being destroyed
under a live session. The guest named it once the gate started capturing
kernel diagnostics:

  mutable-fs: open fd=2 path=/tmp/freebsd-sftp flags=0xe
  mutable-fs: write-fd fd=2 bytes=35 cursor=35
  user: rejected syscall=14 arg0=0x2 reason=fs-close-denied

The write landed; the close was refused. Transient processes and SSH child
channels all draw from the first free process-table slot, so in one run a
single pid -- 32 -- was issued and reaped nineteen times, and every child
channel ran as child=32. VFS handles and kernel sockets were keyed on that
pid, and syscall_release_process_resources sweeps every resource owned by it
when a process is reaped. Any transient exiting while an SFTP or outbound SSH
session had a file open therefore released that session's handle, and the
session failed on its next operation. Which run it hit depended purely on
overlap, which is why it only appeared under slow emulation and never
reproduced on a fast host.

Give each process incarnation a token that is never issued twice, and key VFS
handles and kernel sockets on it instead of the pid. The pid keeps its
meaning for scheduling, reporting and diagnostics; only ownership moves. The
VFS and socket layers are unchanged: they already compared an opaque owner
value, and now that value no longer collides.

copy_process enumerates fields by hand, so the token also has to be copied
there. It was missed at first and every socket allocation failed with owner
zero, taking sshd's listener down with boot error 2301 -- worth knowing about
before adding another field to that struct.

Verified: the FreeBSD bidirectional suite passes locally end to end, and
qemu-smoke, qemu-local-console-gate, qemu-storage-crash, qemu-regression-suite,
filesystem milestone 62, qemu-persistence-reboot and compile-check all pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Pummelchen Pummelchen changed the title Fix three CI failures, and unblock the FreeBSD gate's first timeout Repair CI, the local console, and process resource ownership Aug 23, 2026
@Pummelchen
Pummelchen merged commit 8c8ea41 into main Aug 23, 2026
15 of 17 checks passed
@Pummelchen
Pummelchen deleted the fix/ci-compile-check-and-abi-contract branch August 25, 2026 14:58
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