Files
RedBear-OS/local/docs/QT6-WAYLAND-NULL8-DIAGNOSIS.md
T
vasilito e4c4cb59e4 docs: Qt6 Wayland null+8 static diagnosis — patch verdict + instrumented runbook
Phases A-C static diagnosis of the wl_proxy_add_listener null+8 crash.
Evidence-backed root cause, candidate patch verdict, ruled-out hypothesis
cross-check, and a copy-pasteable instrumented-rebuild runbook for the
orchestrator.

Verdict: candidate patch qtwaylandscanner-null-guard-listeners.patch guards
the CORRECT site (every generated init_listener). Necessary but not
sufficient alone — complementary to libwayland redox.patch hunk 1.
v5.5 'verified FIXED' claim overstated: never tested in isolation,
kded6 workaround masked it, stale-sysroot risk documented.
2026-07-20 09:52:08 +09:00

25 KiB
Raw Blame History

Qt6 Wayland wl_proxy_add_listener null+8 Crash — Static Diagnosis (Phases AC)

Status: STATIC DIAGNOSIS COMPLETE. Patch verdict issued. Instrumented-rebuild runbook below for the orchestrator to execute (Phase A instrumentation + forced rebuild chain + QEMU observation). No QEMU runs or multi-hour Qt rebuilds were performed in this diagnosis pass — host contention constraints honoured.

Date: 2026-07-20 Branch: 0.3.1 Scope: Phases AC of WAYLAND-IMPLEMENTATION-PLAN.md § "Current Blocker"


1. VERDICT (root cause statement)

The NULL proxy at the crash is the wl_registry* returned by wl_display_get_registry(mDisplay) in QWaylandDisplay::setupConnection() (qwaylanddisplay.cpp:340), and the crash site is the qtwaylandscanner-generated QtWayland::wl_registry::init_listener() calling wl_registry_add_listener(NULL, ...)wl_proxy_add_listener(NULL, ...)proxy->object.implementation = listener faulting at address 0x8.

A secondary, alternative crash site (consistent with the commit-message root cause but NOT with the "before any compositor events" signature) is any generated init_listener() for a global bound via wlRegistryBind() during QWaylandDisplay::registry_global() (qwaylanddisplay.cpp:634).

The candidate patch (local/patches/qtbase/qtwaylandscanner-null-guard-listeners.patch) guards both sites because it modifies the code generator to emit if (m_<iface>) <iface>_add_listener(...) for every generated init_listener().

Patch verdict: the candidate patch guards the CORRECT site. It is necessary but not sufficient alone — see §4.


2. EVIDENCE CHAIN (every claim cites file:line)

2.1 Crash signature confirmation

Claim Evidence
Fault address 0x8 = write to proxy->object.implementation local/recipes/wayland/libwayland/source/src/wayland-client.c:665proxy->object.implementation = implementation;
wl_object.implementation is at offset 8 local/recipes/wayland/libwayland/source/src/wayland-private.h:70-74struct wl_object { const struct wl_interface *interface; /* off 0 */ const void *implementation; /* off 8 */ uint32_t id; /* off 16 */ };
wl_proxy.object is at offset 0 local/recipes/wayland/libwayland/source/src/wayland-client.c:63-74struct wl_proxy { struct wl_object object; ... }; (first member)
Therefore proxy==NULL faults at NULL+8 on the implementation write Confirmed: *(uintptr_t*)((char*)NULL + 8) = listener = SIGSEGV at 0x8

2.2 Qt6 client init sequence (where the first add_listener fires)

Qt 6.11.1 ships the entire Wayland QPA client plugin inside qtbase (not qtwayland). Confirmed locations:

File Role
local/recipes/qt/qtbase/source/src/plugins/platforms/wayland/qwaylanddisplay.cpp QWaylandDisplay (the main client-side display object)
local/recipes/qt/qtbase/source/src/plugins/platforms/wayland/qwaylanddisplay_p.h:105 class QWaylandDisplay : public QObject, public QtWayland::wl_registryQWaylandDisplay IS-A wl_registry wrapper
local/recipes/qt/qtbase/source/src/tools/qtwaylandscanner/qtwaylandscanner.cpp The code generator (vendored into qtbase, not qtwayland)

Init sequence (qwaylanddisplay.cpp):

Line 322:  QWaylandDisplay::QWaylandDisplay(...)
Line 327:    mDisplay = wl_display_connect(nullptr);
Line 328:    if (mDisplay) {              ← rules out NULL display
Line 329:      setupConnection();
Line 330:    }

Line 338:  void QWaylandDisplay::setupConnection()
Line 340:    struct ::wl_registry *registry = wl_display_get_registry(mDisplay);  ← CAN return NULL
Line 341:    init(registry);              ← calls generated QtWayland::wl_registry::init(wl_registry*)

The generated init(obj) (qtwaylandscanner.cpp:1155-1160):

void wl_registry::init(struct ::wl_registry *obj)
{
    m_wl_registry = obj;          // line 1157 — assigned BEFORE init_listener
    if (hasEvents)
        init_listener();          // line 1159 — called AFTER assignment
}

The generated init_listener() (qtwaylandscanner.cpp:1295-1298, PATCHED):

void wl_registry::init_listener()
{
    if (m_wl_registry) wl_registry_add_listener(m_wl_registry, &m_wl_registry_listener, this);
}

Therefore: if wl_display_get_registry() returns NULL → m_wl_registry = NULL → patched init_listener() skips the add_listener call → no crash. Unpatched (upstream Qt 6.11.1) → wl_registry_add_listener(NULL, ...)wl_proxy_add_listener(NULL, ...)page fault at 0x8.

2.3 Why wl_display_get_registry() can return NULL

wl_display_get_registry() is generated code (see local/recipes/wayland/libwayland/source/tests/data/example-client.h:1027-1036):

static inline struct wl_registry *
wl_display_get_registry(struct wl_display *wl_display)
{
    struct wl_proxy *registry;
    registry = wl_proxy_marshal_flags((struct wl_proxy *) wl_display,
                 WL_DISPLAY_GET_REGISTRY, &wl_registry_interface, ...);
    return (struct wl_registry *) registry;   // NULL if marshal_flags returns NULL
}

wl_proxy_marshal_flagswl_proxy_marshal_array_flags (wayland-client.c:901-963) returns NULL when:

Condition Source
create_outgoing_proxy()proxy_create() OOM (zalloc fails) wayland-client.c:487
wl_map_insert_new returns 0 (client ID space exhausted) wayland-client.c:496-499
display->last_error already set (fatal display error) wayland-client.c:922-924

The third condition is the most plausible: if the implicit display handshake during wl_display_connect() encounters a protocol or socket error, display->last_error is set, and the subsequent wl_display_get_registry() silently returns NULL without even attempting proxy creation.

2.4 Candidate patch guards the correct site

The candidate patch (local/patches/qtbase/qtwaylandscanner-null-guard-listeners.patch) modifies the generator at qtwaylandscanner.cpp:1297 to emit if (m_%s) %s_add_listener(...) instead of %s_add_listener(...).

This single generator change guards the init_listener() of every generated Wayland wrapper class — wl_registry, wl_compositor, wl_shm, wl_seat, wl_output, xdg_wm_base, and all zwp_*/wp_* extensions.

The patch is:

  • Confirmed applied to the live source tree (qtwaylandscanner.cpp:1297 shows the patched 4-argument printf).
  • Confirmed wired in local/recipes/qt/qtbase/recipe.toml patches list (line 14: "qtwaylandscanner-null-guard-listeners.patch").
  • Confirmed applicable to Qt 6.11.1 (context lines match; 3-arg → 4-arg printf transformation is unique).

2.5 Complementary guard already in libwayland

A second, independent NULL guard exists in local/patches/libwayland/redox.patch (added 2026-05-06 in commit ea125b0b45), which modifies wl_proxy_add_listener itself (wayland-client.c:652-656):

if (!proxy) {
    fprintf(stderr, "FATAL: wl_proxy_add_listener(NULL) caller=%p -- returning error\n",
            __builtin_return_address(0));
    return -1;
}

This converts the page fault into a logged error + return -1. If this guard is active, the null+8 page fault CANNOT occur — instead you see the FATAL log line. The presence of the page-fault crash signature therefore implies the libwayland guard was not yet compiled into the running binary at the time of observation (stale libwayland in qtbase's sysroot — see §6).

The two guards are complementary, not redundant in purpose:

  • qtwaylandscanner patch: prevents Qt from calling wl_*_add_listener(NULL) (clean — Qt proceeds with a no-op listener)
  • libwayland guard: prevents the crash if any caller (Qt or otherwise) passes NULL (defensive — logs + returns error, caller may misbehave)

Neither guard addresses the root cause: why proxy creation returns NULL.


3. CROSS-CHECK OF THE FOUR RULED-OUT HYPOTHESES

# Hypothesis Plan's Verdict Re-evaluation Evidence
1 relibc calloc doesn't zero FALSE SOUND local/sources/relibc/src/header/stdlib/mod.rs:281ptr.write_bytes(0, size) runs unconditionally on every successful malloc. Overflow path returns NULL + ENOMEM (line 287-288). Verified.
2 Compositor protocol bug FALSE SOUND Compositor is pure-Rust (redbear-compositor), protocol-compliant, doesn't crash. Client crashes during its own init.
3 wl_display_connect() returns NULL FALSE SOUND qwaylanddisplay.cpp:327-329 — Qt checks if (mDisplay) before calling setupConnection(). A NULL display never reaches get_registry.
4 wl_display_get_registry() returns NULL FALSE PARTIALLY UNSOUND — the plan claims "My null guard if (!registry) _exit(1) catches this", but NO such guard exists at qwaylanddisplay.cpp:340-341. The code is struct ::wl_registry *registry = wl_display_get_registry(mDisplay); init(registry); — unconditional. The hypothesis is only effectively ruled out because the candidate patch's init_listener() guard implicitly handles NULL. Without the patch, this is the leading crash cause.
5 libwayland proxy_create() returns NULL UNLIKELY WEAKENED — the plan says "Qt would catch NULL", but Qt does NOT check wlRegistryBind() / wl_display_get_registry() return values. That's exactly the gap the candidate patch closes. The hypothesis is plausible, not unlikely.

Net effect: hypotheses 13 are soundly ruled out. Hypothesis 4's cited evidence (explicit _exit guard) does not exist in the source — the ruling holds only because of the candidate patch. Hypothesis 5 is under-weighted: NULL returns from proxy creation are not caught by Qt without the patch.


4. PATCH VERDICT

Candidate: local/patches/qtbase/qtwaylandscanner-null-guard-listeners.patch

Verdict: CORRECT SITE, NECESSARY, BUT NOT SUFFICIENT ALONE.

Question Answer
Does the crash happen inside scanner-generated init_listener()? YESwl_proxy_add_listener is only reachable from wl_*_add_listener wrappers, which in Qt's client init path are only emitted by qtwaylandscanner inside init_listener(). The hand-written wl_callback_add_listener / wl_buffer_add_listener sites (see §5) all fire LATER, after the first roundtrip.
Does the patch guard the actual crash site? YES — the generator change emits if (m_<iface>) before every wl_*_add_listener(m_<iface>, ...) in every generated init_listener().
Is the patch the same as upstream commits de2d74c37e/882c2974ec? de2d74c37e/882c2974ec ARE the Red Bear commits that introduced this patch. They are not upstream Qt commits. de2d74c37e (2026-05-06 17:18) made the direct source edit; 882c2974ec (2026-05-07) extracted it into the patch file + wired recipe.toml.
Was the patch runtime-validated? NO — not in isolation. Commit de2d74c37e claims "VERIFIED: greeter UI boots", but this was done with THREE fixes simultaneously applied (qtwaylandscanner patch + libwayland NULL guard from ea125b0b45 13:44 same day + kded6 workaround renaming libqwayland.so to .disabled). The qtwaylandscanner patch alone was never proven. The "7 globals" claim in the commit message also disagrees with the compositor's actual 8 globals, suggesting incomplete observation.
Is a corrected patch needed? NO for the reported crash. The candidate guards the correct site. Additional hardening for non-scanner sites is recommended (§5) but is a separate concern.

Why "not sufficient alone"

The patch prevents Qt from calling wl_*_add_listener(NULL), but it does NOT fix the root cause: why wl_display_get_registry() or wlRegistryBind() returns NULL in the first place. With the patch applied, Qt silently proceeds with m_wl_registry == nullptr, the registry listener is never attached, Qt receives no global announcements, and the desktop session fails to initialise (no screens, no compositor, no input devices) — a different failure mode than a page fault, but still a non-functional desktop.

The root cause requires Phase A instrumentation + Phase C libwayland audit to determine which of the three NULL-return paths (OOM, map exhaustion, or display->last_error) is firing.

Relationship to existing patches

Patch Location Status Relationship
qtwaylandscanner-null-guard-listeners.patch local/patches/qtbase/ Candidate (this diagnosis) Generator-level guard — prevents the call
libwayland redox.patch hunk 1 local/patches/libwayland/redox.patch Applied (commit ea125b0b45) Library-level guard — prevents the crash if call happens
patch-wayland-gen.sh local/recipes/qt/qtbase/ ORPHANED — never invoked from recipe.toml Legacy sed-based post-build patcher (replaced by the generator patch)
qtwayland-empty-cursor-guards.patch local/recipes/qt/qtbase/ ORPHANED — not in recipe.toml patches list Unrelated (cursor theme empty-image guards)
P0-fix-broken-include.patch, P1-qplatformopengl-guard.patch local/patches/qtbase/ ORPHANED — symlinks exist in recipe dir but not in patches list Not applied to current builds

No file changes are needed to the candidate patch. It is correct. The runbook below (§7) validates it end-to-end.


5. UNGUARDED add_listener SITES (hardening recommendations — NOT the crash site)

These hand-written wl_*_add_listener calls are NOT guarded by the candidate patch (which only covers scanner-generated code). All fire AFTER the first roundtrip, so they are NOT the "before any compositor events" crash site. Listed for completeness; a separate hardening patch could address them:

File:Line Call When it fires
qwaylanddisplay.cpp:1049 wl_callback_add_listener(mSyncCallback, &syncCallbackListener, this) requestWaylandSync() — during sync/roundtrip
qwaylandbuffer.cpp:27 wl_buffer_add_listener(buf, &listener, this) Buffer creation — during surface commit
qwaylandwindow.cpp:1807 wl_callback_add_listener(mFrameCallback, &callbackListener, this) Frame callback — during surface render
qwaylandtextinputv2.cpp:178 wl_callback_add_listener(m_resetCallback, &callbackListener, this) Text input reset — during IME interaction
qwaylandbrcmeglwindow.cpp:50 wl_buffer_add_listener(mBuffer, &buffer_listener, this) Broadcom EGL — hardware integration (not loaded on Redox)

These are all covered by the libwayland-level NULL guard (redox.patch hunk 1), so they will log FATAL + return -1 rather than crash. A future hardening patch to Qt would add explicit if (ptr) checks at each site for cleaner diagnostics.


6. WHY THE CRASH PERSISTED DESPITE "VERIFIED FIXED" (May 2026)

The v5.5 changelog (CONSOLE-TO-KDE-DESKTOP-PLAN.md:38-46) and four other locations claim the crash is "verified FIXED" via commits de2d74c37e + 882c2974ec. This claim is overstated for three reasons:

  1. Never tested in isolation. The May 6 verification had three fixes applied simultaneously. The qtwaylandscanner patch was never proven to fix the crash on its own.

  2. The kded6 workaround masked it. Commit 176624a008 (2026-05-06 16:34, one hour before de2d74c37e) deployed a wrapper script that renames libqwayland.so to .disabled before launching kded6, preventing Qt from loading the Wayland plugin at all. The "greeter UI boots" verification may have been of a greeter running WITHOUT the Wayland plugin (offscreen QPA), not WITH the patched Wayland plugin.

  3. Stale-sysroot rebuild chain. The WAYLAND-IMPLEMENTATION-PLAN.md:157-171 documents that "Modifying only libwayland does NOT update qtbase's static copy of libwayland-client.a. The old binary persists in qtbase's sysroot." If the full chain (libwayland → qtbase → qtdeclarative → qtwayland) was not force-rebuilt, the running binary may have linked against an unpatched libwayland, making the page fault reappear.

The crash re-appears as the active blocker in WAYLAND-IMPLEMENTATION-PLAN.md (dated 2026-07-08, two months after the "fix") and CONSOLE-TO-KDE-DESKTOP-PLAN.md:234 (2026-06-20, "WIRED, UNVALIDATED"). The "verified FIXED" line at CONSOLE-TO-KDE-DESKTOP-PLAN.md:43 directly contradicts line 240-243 of the same document.

The v5.5 claim should be corrected to "WIRED, NEVER RUNTIME-VALIDATED IN ISOLATION" until the Phase A runbook below produces positive QEMU evidence.


7. INSTRUMENTED-REBUILD RUNBOOK (Phase A diagnostics + forced chain)

This runbook is for the orchestrator to execute when host contention allows. It produces definitive evidence of WHICH proxy is NULL and WHY.

7.1 Phase A: Add fprintf diagnostics (3 files)

All edits are to the live source trees. They will be discarded on next repo fetch, so mirror them into patches if durability is needed.

Edit 1: local/recipes/qt/qtbase/source/src/plugins/platforms/wayland/qwaylanddisplay.cpp

In setupConnection() (around line 340), add:

void QWaylandDisplay::setupConnection()
{
    struct ::wl_registry *registry = wl_display_get_registry(mDisplay);
    fprintf(stderr, "[RB-DIAG] setupConnection: mDisplay=%p registry=%p\n", (void*)mDisplay, (void*)registry);
    init(registry);
    fprintf(stderr, "[RB-DIAG] setupConnection: init() returned, m_wl_registry=%p\n", (void*)object());

Edit 2: local/recipes/qt/qtbase/source/src/tools/qtwaylandscanner/qtwaylandscanner.cpp

In the init_listener() generator (around line 1295), add a diagnostic printf INSIDE the generated function. Change the patched line to:

printf("        fprintf(stderr, \"[RB-DIAG] init_listener m_%s=%p\\n\", (void*)m_%s);\n", interfaceName, interfaceName);
printf("        if (m_%s) %s_add_listener(m_%s, &m_%s_listener, this);\n", interfaceName, interfaceName, interfaceName, interfaceName);
printf("        else fprintf(stderr, \"[RB-DIAG] SKIPPED add_listener for %s (NULL proxy)\\n\");\n", interfaceName);

Edit 3: local/recipes/wayland/libwayland/source/src/wayland-client.c

In wl_proxy_marshal_array_flags (around line 920), add before the display->last_error check:

if (display->last_error)
    fprintf(stderr, "[RB-DIAG] marshal_flags: display->last_error=%d, opcode=%u\n",
            display->last_error, opcode);

And in proxy_create (around line 487), add:

if (proxy == NULL) {
    fprintf(stderr, "[RB-DIAG] proxy_create: zalloc FAILED (OOM)\n");
    return NULL;
}

7.2 Phase B: Force-rebuild chain (in strict order)

The chain MUST be rebuilt in this order. Skipping any step leaves a stale binary in the next recipe's sysroot.

# Step 0: Rebuild prefix (relibc headers if Phase A edit 3 touched wayland-client.c)
touch relibc && make prefix

# Step 1: Force-rebuild libwayland (Phase A edit 3)
rm -rf local/recipes/wayland/libwayland/target
rm -f repo/x86_64-unknown-redox/libwayland.pkgar
./target/release/repo cook libwayland --force-rebuild

# Step 2: Force-rebuild qtbase (Phase A edits 1 + 2; picks up new libwayland)
rm -rf local/recipes/qt/qtbase/target
rm -f repo/x86_64-unknown-redox/qtbase.pkgar
./target/release/repo cook qtbase --force-rebuild

# Step 3: Force-rebuild qtdeclarative (links against new qtbase)
rm -rf local/recipes/qt/qtdeclarative/target
rm -f repo/x86_64-unknown-redox/qtdeclarative.pkgar
./target/release/repo cook qtdeclarative --force-rebuild

# Step 4: Force-rebuild qtwayland (uses qtwaylandscanner from new qtbase)
rm -rf local/recipes/qt/qtwayland/target
rm -f repo/x86_64-unknown-redox/qtwayland.pkgar
./target/release/repo cook qtwayland --force-rebuild

# Step 5: Rebuild the full image
./local/scripts/build-redbear.sh redbear-full

If repo cook is unavailable, use make r.<recipe> + make cr.<recipe> (clean + rebuild) equivalents.

7.3 Phase C: QEMU invocation + serial-log markers

# Boot the instrumented image with serial console captured
qemu-system-x86_64 \
    -cdrom build/x86_64/redbear-full.iso \
    -m 4096 \
    -serial file:/tmp/rb-wayland-diag.log \
    -netdev user,id=n0 -device e1000,netdev=n0 \
    -display none \
    2>&1 | tee /tmp/rb-qemu-stdout.log

# After boot, trigger a Wayland client (greeter or smoke test)
# Inside the guest (via serial console):
#   export QT_QPA_PLATFORM=wayland
#   export WAYLAND_DISPLAY=wayland-0
#   /usr/lib/qt6/examples/wayland/minimal-qml   # or any Qt6 Wayland client

7.4 Markers to observe in /tmp/rb-wayland-diag.log

Marker Meaning Action
[RB-DIAG] setupConnection: mDisplay=0x... registry=0x... Connection established; check if registry is NULL If registry=(nil), root cause is in wl_display_get_registry
[RB-DIAG] setupConnection: init() returned, m_wl_registry=0x... Registry wrapper initialised If (nil), the patched init_listener skipped
[RB-DIAG] init_listener m_wl_registry=0x... Generated init_listener executed for wl_registry Confirms the scanner-generated code runs
[RB-DIAG] SKIPPED add_listener for wl_registry (NULL proxy) The candidate patch fired — NULL was caught Patch works. Root cause is upstream (proxy creation).
[RB-DIAG] marshal_flags: display->last_error=N Display had a prior fatal error Root cause: display handshake failure. Investigate socket/IPC.
[RB-DIAG] proxy_create: zalloc FAILED (OOM) Out of memory during proxy allocation Root cause: OOM. Increase guest RAM or audit relibc allocator.
FATAL: wl_proxy_add_listener(NULL) caller=0x... libwayland guard fired (some caller passed NULL) The candidate patch did NOT catch this site — the NULL came from a non-scanner call site (§5). Use addr2line on the caller address.
Page fault: 0000000000000008 US The crash persisted — BOTH guards failed to apply Stale binary in sysroot. Re-run the forced rebuild chain.

7.5 Decision tree after observation

Observe the FIRST [RB-DIAG] or crash marker:

├── registry=(nil) in setupConnection
│   ├── display->last_error set  →  ROOT CAUSE: display handshake failure (IPC/socket)
│   ├── zalloc FAILED           →  ROOT CAUSE: OOM in proxy_create
│   └── neither                  →  ROOT CAUSE: wl_map_insert_new returned 0 (investigate)
│
├── registry=0x... (non-NULL) but SKIPPED add_listener
│   └── Patch works; root cause is elsewhere in global binding (registry_global path)
│
├── FATAL: wl_proxy_add_listener(NULL) caller=0x...
│   └── addr2line -e <binary> <caller_addr>  →  identifies the exact unguarded call site (§5)
│
└── Page fault at 0x8 (no markers)
    └── Stale binary — repeat forced rebuild chain with --no-cache

8. SUMMARY TABLE

Question Answer
Which proxy is NULL? wl_registry* from wl_display_get_registry() (primary) or any wl_<iface>* from wlRegistryBind() (secondary)
Why is it NULL? wl_proxy_marshal_array_flags returns NULL: OOM, map exhaustion, or display->last_error set
Where is the crash? Generated init_listener()wl_*_add_listener(NULL)wl_proxy_add_listener(NULL)proxy->object.implementation = listener at wayland-client.c:665
Does the patch guard the crash site? YESqtwaylandscanner.cpp:1297 emits if (m_<iface>) before every generated add_listener
Is the patch sufficient alone? NO — prevents crash but not the root cause (NULL proxy creation); also redundant with libwayland redox.patch hunk 1
Is a corrected patch needed? NO — the candidate is correct for the reported crash
Was the v5.5 "verified FIXED" sound? NO — never tested in isolation; kded6 workaround masked it; stale-sysroot risk
Are the ruled-out hypotheses sound? Hypotheses 13 sound; 4 cites non-existent evidence; 5 is under-weighted

9. FILES CHANGED IN THIS DIAGNOSIS

File Change
local/docs/QT6-WAYLAND-NULL8-DIAGNOSIS.md NEW — this report
local/patches/qtbase/ No changes — candidate patch is correct
local/recipes/qt/qtbase/recipe.toml No changes — patch is already wired (line 14)

10. NEXT STEPS (for the orchestrator)

  1. Execute the Phase A runbook (§7) when host contention allows. The fprintf diagnostics will definitively identify which NULL-return path fires.
  2. Do NOT remove the candidate patch — it is correct and necessary.
  3. Do NOT trust the "verified FIXED" claims in CONSOLE-TO-KDE-DESKTOP-PLAN.md:43, config/redbear-full.toml:183, local/recipes/kde/kwin/recipe.toml:2, or local/docs/archived/IMPLEMENTATION-MASTER-PLAN.md:61 until positive QEMU evidence from §7.4 confirms the crash is gone.
  4. If the runbook reveals display->last_error as the root cause, the fix is in the IPC/socket layer (kernel or relibc), not in Qt or libwayland.
  5. If the runbook reveals OOM, increase guest RAM or audit the relibc allocator (dlmalloc) for fragmentation under Qt6's allocation pattern.