commit 2e0bfd1f6351b55073c8fa70c933fc8f2cf2ffb6
parent 8162aedae1bdd2d3936491779d4416403da36777
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Fri, 9 Oct 2026 00:05:13 -0400
compose-chrome: the renderer runs without DISPLAY or WAYLAND_DISPLAY
It is headless and needs no display, and a stale one breaks it: with DISPLAY naming an X server that has gone (Xwayland killed by the OOM killer), ANGLE's SwiftShader tries to connect, fails, and every render dies with "assertSwiftShader ... vendor=''". Found by the video sessions after an OOM on 2026-10-08.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
2 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/umtool/report-to-video/compose-chrome.mjs b/umtool/report-to-video/compose-chrome.mjs
@@ -734,9 +734,21 @@ async function framesOnDisk(dir) {
}
/** Run the renderer, its chatter to stderr (stdout may be a build's NDJSON). */
+/**
+ * The renderer's environment: ours without DISPLAY and WAYLAND_DISPLAY. It is
+ * headless and needs no display, and a stale one breaks it: with DISPLAY naming
+ * an X server that has gone (Xwayland killed by the OOM killer, say), ANGLE's
+ * SwiftShader tries to connect to it, fails, and every render dies with
+ * "assertSwiftShader ... vendor=''".
+ */
+export function rendererEnv(env = process.env) {
+ const { DISPLAY: _d, WAYLAND_DISPLAY: _w, ...rest } = env;
+ return rest;
+}
+
function runRenderer(cmd, args) {
return new Promise((resolve, reject) => {
- const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
+ const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"], env: rendererEnv() });
let tail = "";
const keep = (b) => {
process.stderr.write(b);
@@ -928,7 +940,7 @@ export async function composeChrome({
"--virtual-time-budget=6000",
`--screenshot=${out}`,
`file://${path.join(projDir, "index.html")}?still=${Number(still)}`,
- ], { maxBuffer: 1 << 26 });
+ ], { maxBuffer: 1 << 26, env: rendererEnv() });
return { ...result, still: out };
}
diff --git a/umtool/report-to-video/threads.test.mjs b/umtool/report-to-video/threads.test.mjs
@@ -187,3 +187,9 @@ test("railLayout: cards share the height, capped, the stack centred", () => {
assert.equal(three.h, 190);
assert.equal(three.top, Math.floor((886 - 3 * 190 - 2 * 14) / 2));
});
+
+test("the renderer runs without a display: a stale DISPLAY would break SwiftShader", async () => {
+ const { rendererEnv } = await import("./compose-chrome.mjs");
+ const env = rendererEnv({ DISPLAY: ":0.0", WAYLAND_DISPLAY: "wayland-0", PATH: "/usr/bin", HOME: "/h" });
+ assert.deepEqual(env, { PATH: "/usr/bin", HOME: "/h" });
+});