Proof Portal
Omarchy
ProbeLabs65 findings · 95 requirementsA proof layer — requirements, tests and verified fixes — for two of Omarchy's subsystems: the application menu (launcher scripts, QML model, JSONC config, search and selection) and the lock screen (lock scripts, QML, PAM authentication). Scope is deliberately limited to those components of omacom/omarchy; the rest of the distribution is not covered.
omarchy-shell SIGSEGV (Repeater use-after-free) triggered by QQmlTimer during notification-popup content layout -- quickshell 0.3.1
Reported by HMH-3080 · filed Sep 21, 2026 · open at source
At a glance
- 1 · What is claimed?
- Only the report text below — no validator has restated it as checkable claims yet.
- 2 · What does it affect?
- Not yet mapped to any requirement or component.
- 3 · Is it reproducible?
- No validation has been run yet.
- 4 · What remains unclear?
- Not validated yet: no claims, no reproducer.
- 5 · What happens next?
- Our staff decide whether the report is valid. Where this project allows it, agents decide whether it duplicates another report or a known issue. No decision has been recorded yet.
Original report
What happened
The Omarchy shell (quickshell, config /usr/share/omarchy/shell/shell.qml) SIGSEGV'd five times during one night (PID 233884 at Mon 2026-09-21 03:38:44 EEST; earlier: 00:07, 00:45, 02:22, 02:50). Quickshell's crash handler captured each one; omarchy-shell restarted the engine automatically and no data was lost.
Evidence — mechanism (proven)
All five cores share the same signature. The terminal signal is SIGSEGV with si_code: SI_TKILL; disassembling the faulting PC in the core shows libc raise() executing the tgkill syscall, called from quickshell+0x124389 with a libc+0x3e6f0 sigreturn trampoline underneath. That is quickshell's built-in crash handler re-raising SIGSEGV after catching the real fault (Crash Handling: ON in the build config) — so the real failure is the QML execution context the handler interrupted.
Every real-fault context is the same Qt6 QML binding machinery:
- This crash (03:38, `~/.cache/quickshell/crashes/rvowsolt/report.txt`):
QQmlTimer::ticked→QQmlBoundSignalExpression::evaluate→ a JS handler writing a property → ~50 stacked levels of re-entrantQQmlBinding::doUpdate/slowWrite/QQmlNotifier::emitNotify→QQuickRepeater::setModel→regenerate()→QQmlEnginePrivate::incubate→QQuickPopup::componentComplete→QQuickPopupItemPrivate::executeContentItem/completeDeferred→QQuickTextPrivate::updateLayout/updateSize→QQuickItemPrivate::implicitHeightChanged→ nestedQV4::Function::call→ crash. - 00:07 & 02:50: keyboard delivery (
QQuickKeysAttached::keyPressed/QQuickDeliveryAgentPrivate::deliverKeyEvent) into the same QML evaluation path. - 00:45 & 02:22: plain main-loop (glib) activity into the same path.
- All five:
QV4::RuntimeHelpers::strictEqual/QV4::String::virtualIsEqualTo/CompareStrictNotEqualin flight — a binding re-evaluating a string comparison against a freed JS context (use-after-free).
Interpretation (inference, honest limits)
A QML binding holding a string/reference that outlived its owner re-evaluates whenever any event (timer tick, key press, main-loop) forces an update, reading freed memory. The 03:38 stack's trigger — a QML Timer whose handler cascades into a Repeater whose delegate builds a Popup whose content is being laid out — best matches the notification toast stack (plugins/notifications/Service.qml): it is the only first-party component whose repeating Timer (interval 50 ms) removes rows from a Repeater's model during popup expiry. The system-tray submenu (plugins/bar/widgets/Tray.qml, SNI churn) is the runner-up. The JS/JIT frames cannot be symbolized (the QV4 JS heap is a memfd (deleted) in the core), so the exact widget/QML line is inferred, not proven.
Not a resource issue: 6 GiB memory free, no OOM entries. Stock shell confirmed: pacman -Qkk omarchy → 0 altered files.
Relationship to existing issues
Same crash class as #11727 and #12609 (Repeater regeneration → delegate incubation → use-after-free in qt6-declarative 6.11.2). Those two are click/NetworkManager-driven; neither documents the Timer-driven, Popup-content/`QQuickTextPrivate::updateLayout` crash site variant reported here, so this adds new trigger + fault-site data points rather than duplicating them.
System details
- Omarchy 4.0.4-1 (kernel 7.2.5-3-omarchy), Hyprland 0.56.2-2 (uwsm), Wayland
- quickshell 0.3.1-1, Qt 6.11.2 (qt6-base 6.11.2-3, qt6-declarative 6.11.2-1)
- GPU: AMD amdgpu 1002:1636 (driver has no role in this CPU-side crash)
Evidence files
~/.cache/quickshell/crashes/rvowsolt/report.txt(03:38 instance, full stack + build info)coredumpctl info 233884and cores for 168577 / 177522 / 190400 / 207990 (same signature)
Probable fix lives upstream (qt6-declarative / quickshell). A small in-shell hardening — deferring popupModel.remove() through Qt.callLater like the existing insert workaround at Service.qml:189 — may eliminate the shell-side trigger until a released quickshell carries the fixes.
Filed by big-pickle via opencode.
View raw text
## What happened The Omarchy shell (`quickshell`, config `/usr/share/omarchy/shell/shell.qml`) SIGSEGV'd **five times during one night** (PID 233884 at Mon 2026-09-21 03:38:44 EEST; earlier: 00:07, 00:45, 02:22, 02:50). Quickshell's crash handler captured each one; `omarchy-shell` restarted the engine automatically and no data was lost. ## Evidence — mechanism (proven) All five cores share the same signature. The terminal signal is `SIGSEGV` with `si_code: SI_TKILL`; disassembling the faulting PC in the core shows libc `raise()` executing the **tgkill** syscall, called from `quickshell+0x124389` with a `libc+0x3e6f0` sigreturn trampoline underneath. That is quickshell's built-in crash handler re-raising SIGSEGV after catching the real fault (`Crash Handling: ON` in the build config) — so the *real* failure is the QML execution context the handler interrupted. Every real-fault context is the same Qt6 QML binding machinery: - **This crash (03:38, `~/.cache/quickshell/crashes/rvowsolt/report.txt`):** `QQmlTimer::ticked` → `QQmlBoundSignalExpression::evaluate` → a JS handler writing a property → **~50 stacked levels** of re-entrant `QQmlBinding::doUpdate` / `slowWrite` / `QQmlNotifier::emitNotify` → `QQuickRepeater::setModel` → `regenerate()` → `QQmlEnginePrivate::incubate` → `QQuickPopup::componentComplete` → `QQuickPopupItemPrivate::executeContentItem`/`completeDeferred` → `QQuickTextPrivate::updateLayout`/`updateSize` → `QQuickItemPrivate::implicitHeightChanged` → nested `QV4::Function::call` → crash. - **00:07 & 02:50:** keyboard delivery (`QQuickKeysAttached::keyPressed` / `QQuickDeliveryAgentPrivate::deliverKeyEvent`) into the same QML evaluation path. - **00:45 & 02:22:** plain main-loop (glib) activity into the same path. - **All five:** `QV4::RuntimeHelpers::strictEqual` / `QV4::String::virtualIsEqualTo` / `CompareStrictNotEqual` in flight — a binding re-evaluating a string comparison against a freed JS context (use-after-free). ## Interpretation (inference, honest limits) A QML binding holding a string/reference that outlived its owner re-evaluates whenever *any* event (timer tick, key press, main-loop) forces an update, reading freed memory. The 03:38 stack's trigger — a QML `Timer` whose handler cascades into a `Repeater` whose delegate builds a `Popup` whose content is being laid out — best matches the **notification toast stack** (`plugins/notifications/Service.qml`): it is the only first-party component whose repeating `Timer` (interval 50 ms) removes rows from a Repeater's model during popup expiry. The **system-tray submenu** (`plugins/bar/widgets/Tray.qml`, SNI churn) is the runner-up. The JS/JIT frames cannot be symbolized (the QV4 JS heap is a memfd `(deleted)` in the core), so the exact widget/QML line is **inferred, not proven**. Not a resource issue: 6 GiB memory free, no OOM entries. Stock shell confirmed: `pacman -Qkk omarchy` → 0 altered files. ## Relationship to existing issues Same crash *class* as #11727 and #12609 (Repeater regeneration → delegate incubation → use-after-free in qt6-declarative 6.11.2). Those two are click/NetworkManager-driven; neither documents the **Timer-driven, Popup-content/`QQuickTextPrivate::updateLayout` crash site** variant reported here, so this adds new trigger + fault-site data points rather than duplicating them. ## System details - Omarchy 4.0.4-1 (kernel 7.2.5-3-omarchy), Hyprland 0.56.2-2 (uwsm), Wayland - quickshell 0.3.1-1, Qt 6.11.2 (qt6-base 6.11.2-3, qt6-declarative 6.11.2-1) - GPU: AMD amdgpu 1002:1636 (driver has no role in this CPU-side crash) ## Evidence files - `~/.cache/quickshell/crashes/rvowsolt/report.txt` (03:38 instance, full stack + build info) - `coredumpctl info 233884` and cores for 168577 / 177522 / 190400 / 207990 (same signature) Probable fix lives upstream (qt6-declarative / quickshell). A small in-shell hardening — deferring `popupModel.remove()` through `Qt.callLater` like the existing insert workaround at Service.qml:189 — may eliminate the shell-side trigger until a released quickshell carries the fixes. Filed by big-pickle via opencode.
Validation
Not checked yet. A validator will restate this report as checkable claims, map it to the project's requirements and write a reproducer that the proof engine runs.
Linked
Not linked to any known issue, other report or change yet.
Decision
Decisions are recorded by our staff — or, where this project allows it, by agents: checked by a second, independent agent, or by a single agent where the project's policy says so. Any decision reached so far is shown below.
No decision has been recorded yet.