Skip to main content
A first-launch crash on CachyOS turned out to be two different bugs wearing the same error message, and neither could be diagnosed from what Cordial printed. Most of this release is the client learning to say what happened. Text entry also stopped drawing at the wrong size and stopped needing two presses of /.

The settings document now says where it came from

The engine traps with Can't initialize the TaskScheduler before flags have been loaded when it is handed an empty client-settings document. Cordial printed nothing about that document at all, so the crash looked like a race and was diagnosed as one — wrongly, for one of the two reporters. The default launch path now prints the size and the source:
and on a failure says which way it failed: fetched, stale cache, fetch failed, or nothing: with the reason. An unreadable path given with --client-settings now says so instead of silently falling through to the network, which previously looked exactly like a healthy launch with nothing behind it. Measured: a run forced down the empty-document path reproduces the reported crash exactly, printing client settings: 0 bytes before nativeInitClientSettings -> 1; three control runs in the same session each printed 1284288 bytes (cache) and did not crash.

The settings fetch had no timeout at all

It was a bare ureq::get(URL).call() with no connect and no read deadline, so a CDN that accepted a connection and then said nothing would hang the launch indefinitely. It now uses the same timeouts as the updater: 10 seconds to connect, 20 seconds in total.

Focused text was drawn too big

The editor drew text on a focused TextBox noticeably larger than the engine drew it unfocused, so a box visibly jumped as you clicked into it. Roblox’s font mapping carries a fromRbxFontRatio per font id, and the editor ignored it. Measured on font id 46, Builder Sans, ratio 0.7936507937: the capital “S” ink height was 11px as the engine drew it, 15px in the editor before, and 12px after. Thirteen of thirteen boxes checked. Two font ids can share one file with different ratios, so the table stamps each id’s own row rather than the file’s.

”/” opens chat on one press

Pressing / needed two presses to open the chat box. The guard that stops a focused TextBox eating game input was suppressing the key’s release as well: by the time the release arrived the chat box had taken focus, so the engine went on believing / was still held. Releases are now paired to their presses — a release whose press was forwarded is forwarded too. A press suppressed because a box already had focus is never recorded, so its release is still suppressed, which is what keeps typing / into an open chat box from reopening it. Not yet confirmed against a live client. The pairing logic has unit tests and the reasoning is written out at the change, but nobody has driven a real / at a real chat box and watched it. If one press still does not open chat, or if a stray / now appears in the box, that is this change.

A sandboxed plugin dies with the client

A plugin’s sandbox outlived the client that started it. Killing it now kills the whole process group rather than one pid, and the sandbox is started with --unshare-all, --die-with-parent and --new-session. Measured under an injected panic: seven orphaned sandboxes before, none after. The signal case is not covered by that measurement, and is measured in 0.12.1 — see those notes. Drop does not run when the client dies by signal.

Discord Presence uses Cordial’s real application id

It was using a placeholder, so the presence that appeared was not Cordial’s. There is also now a way to change it.

X11 pointer locking survived the move to Wayland

Pointer locking on X11 was reconciled against current main rather than lost, and X11 input now comes from XInput2 with the pointer warp as the fallback. ADR-028 records what the acceleration row does when XI2 is not available.

The update path

Eleven separate faults, starting with the one that ate installs. Both archives now swap as a set or the install is left exactly as it was, rather than being replaced one file at a time.

What is still broken

The startup freeze, when signed in. Not fixed, and now better characterised: two new specimens this cycle, one arriving after the home page rather than before it, and one where the settings menu lost a row immediately before freezing. Reopening usually works. A black canvas inside an experience. Joining works; what you see once there may not. The second half of the CachyOS crash. Two people hit the same error string for different reasons. The reporter whose settings document was rejected is now diagnosable. The reporter whose document was accepted, whose flags carry real values and who crashes later, is not fixed and not yet explained. The web-view dialog cursor is unverified, and the code says so. Whether the cursor reaches a verification or purchase dialog — and whether clicks leak through to the game underneath — has still never been observed live. One report says purchase popups now work; the code that would make them work carries an INFERRED comment saying it has not been run against a live dialog. Both remain true. Empty documents still crash. Deliberately. With no flags loaded the engine’s assertion is telling the truth, and starting anyway would be a stub that lies — failing later somewhere with no relationship to the cause. What changed is that you can now see why in one line.

Nothing that was proposed and measured away

A TaskScheduler gate. The first diagnosis of the CachyOS crash was a startup race, and the fix was to make the client wait for flags before bringing up the TaskScheduler. For the reporter whose document was rejected there is nothing to wait for, and the gate — whose value is captured once and never re-read — would have been permanently shut, turning a crash into a hang. Dropped. Flushing stdout on the crash path. The theory was that a shorter crash transcript meant Rust discarded unflushed output. Rust’s io::Stdout is a LineWriter unconditionally and flushes on every newline regardless of the destination; 200,000 lines followed by an external SIGKILL lost none of them, and the same crash captured to a file and through a pty came out identical. The transcripts differ because the two reporters crash at different points. No flush code was added.