Proof Portal

Project overview

Omarchy

ProbeLabs65 findings · 95 requirements

A 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.

Inbox
ReportR-252GitHub issuePublicReceived

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

Received and recorded verbatim. Nothing has been checked yet.

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

omarchy-shell SIGSEGV (Repeater use-after-free) triggered by QQmlTimer during notification-popup content layout -- quickshell 0.3.1revision 1 · captured 2026-09-28 21:23 UTC via poll

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.

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.