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.
Quickshell SIGSEGV tearing down the workspaces Repeater when Hyprland moves workspaces between monitors
Reported by kevinlouie · filed Aug 27, 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?
- AreaOutside the audited area· Decided by agents · verified
- 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?
- Decided by agents and verified by a second, independent agent — see the decision.
Original report
What happened
Quickshell died with SIGSEGV while Hyprland was shuffling workspaces between monitors during a monitor-profile change. The shell restarted automatically; no user data was lost.
The fault is in the bar's workspaces widget. plugins/bar/widgets/Workspaces.qml rebuilds its Repeater from inside Quickshell's own mid-mutation valuesChanged emit, and its delegates hold live HyprlandWorkspace QObject references.
Symbolized stack
Arch debuginfod, Quickshell 0.3.1, Qt 6.11.2. si_addr = 0x3e8001ad392 (garbage pointer, not null).
#5 QV4::Value::as<QV4::QObjectWrapper> qv4heap_p.h:46 <-- SEGV
#7 QV4::WeakValue::free qv4persistent.cpp:455
#10 QQmlData::destroyed qqmlengine.cpp:1427
#11 QObject::~QObject qobject.cpp:1084
#12 QQmlDMListAccessorData::~QQmlDMListAccessorData qqmldmlistaccessordata.cpp:25
#13 QQmlDelegateModelPrivate::release qqmldelegatemodel.cpp:633
#14 QQuickRepeater::clear qquickrepeater.cpp:422
#15 QQuickRepeater::setModel qquickrepeater.cpp:179
#17 QQmlPropertyPrivate::write qqmlproperty.cpp:1978
#21 QQmlBinding::update qqmlbinding.cpp:166
#22 QQmlNotifier::emitNotify qqmlnotifier.cpp:75
#24 UntypedObjectModel::valuesChanged moc_model.cpp:195
#25 ObjectModel<HyprlandWorkspace>::insertObject core/model.hpp:93 <-- emit mid-insert
#26 ObjectModel<HyprlandWorkspace>::insertObjectSorted
#27 HyprlandIpc::onEvent ipc/connection.cpp:349
#28 HyprlandIpc::eventSocketReady ipc/connection.cpp:136
#33 QAbstractSocketPrivate::canReadNotification qabstractsocket.cpp:673Frame 27 locals identify the event exactly:
name = "7"
existed = falseSo Quickshell had no workspace 7, created one, and emitted valuesChanged() from inside insertObject while the model list was still being mutated. That synchronous emit re-enters QML, which tears the whole Repeater down.
Why the Repeater is torn down at all
plugins/bar/widgets/Workspaces.qml:51:
Repeater {
model: root.workspaceIds()workspaceIds() reads Hyprland.workspaces.values, so the binding is subscribed to valuesChanged, and it returns a fresh JS array on every evaluation. Every workspace add/remove therefore assigns a new model and triggers a full setModel → clear() → destroy-all-delegates cycle — from inside the mid-mutation emit. GridLayout.columns (line 45) binds the same signal.
The delegate holds a live QObject (line 55):
readonly property var workspace: root.workspaceById(modelData)
readonly property bool occupied: workspace !== null && workspace.toplevels.values.length > 0Inference, not proven from the frames: the delegate for modelData == 7 still held a QObjectWrapper for the HyprlandWorkspace destroyed by the earlier destroyworkspace>>7. Destroying that delegate data inside the emit walks a stale V4 persistent slot, which matches the garbage si_addr rather than a null deref. The delegate identity is not recoverable from the available frames.
Trigger / reproduction
Framework Laptop 13 with two external displays, lid transition causing hyprmoncfgd to re-apply the "Desktop Mode" monitor profile while the lock screen was coming up. Quickshell's own log tail immediately before the crash:
21:22:02 omarchy lock ... secure=true
21:22:03 hyprmoncfgd: applied profile "Desktop Mode"; monitoradded:eDP-1
destroyworkspace>>7
monitoradded>>eDP-1
moveworkspace>>1,DP-2
destroyworkspace>>8
moveworkspace>>3,DP-2
workspace>>7 <-- recreates ws 7 (existed=false)
moveworkspace>>5,DP-2
moveworkspace>>7,DP-2
monitorremoved>>eDP-1
21:22:06 SIGSEGVAny monitor add/remove that makes Hyprland destroy and recreate workspaces should reach this. It does not require the lock screen — the lock screen only supplied the monitor churn here.
Not a recent regression
The same crash class was recorded on this machine on 2026-08-16 (QQuickRepeater::regenerate / setModel) under quickshell-git 0.3.0.r20.g28771c7 and Qt 6.11.1, before the 2026-08-25 switch to quickshell 0.3.1. Workspaces.qml is byte-identical to current basecamp/omarchy HEAD.
Relationship to #6952
#6952 is the same class — a Repeater rebuilt from a model-mutation signal with delegates holding live Quickshell QObjects — but not the same bug. That one is PipeWire-driven and faults on the incubation path (VDMListDelegateDataType::createMissingProperties, qqmldmlistaccessordata_p.h:222, null QObject* row). This one is Hyprland-IPC-driven and faults on the destruction path (QQmlDMListAccessorData dtor → QV4::WeakValue::free), in a different file. Filing separately rather than as a comment there, but they likely want the same fix pattern.
Suggested fix
The codebase already has the defense in two places:
plugins/panels/bluetooth/Model.jsdeviceRow()— primitives-only rows, commented "delegates never hold Device QObject wrappers … which segfaults quickshell", with actions re-resolving the live object viadeviceFor().plugins/notifications/Service.qml:189—Qt.callLaterto avoid mutating a model while a Repeater is mid-incubation.
Workspaces.qml does neither. Projecting the delegate to primitives (id, occupied, focused) and resolving the HyprlandWorkspace only at press time would remove the dangling wrapper, and deferring the model reassignment with Qt.callLater would get the reassignment out of Quickshell's mid-mutation emit. Keeping the model array stable when the id set is unchanged would also cut most of the teardown churn, since workspaceIds() returns [1..5] plus extras and usually produces an identical list.
Arguably ObjectModel::insertObject emitting valuesChanged() before the insert has settled deserves an upstream Quickshell report too.
System
Omarchy 4.0.1-1 (stable channel)
quickshell 0.3.1-1
qt6-declarative 6.11.2-1
qt6-base 6.11.2-2
hyprland 0.56.2-1
kernel 7.1.9-arch1-2
hardware Framework Laptop 13 (AMD Ryzen AI 300 Series)
monitors DP-2 ASUS PG32UCDP 3840x2160@120, DP-7 PanaScope PX329 2560x1440@144, eDP-1 internalCrash report on the reporting machine: ~/.cache/quickshell/crashes/pc12k0ckt (PID 1758098, 2026-08-26 21:22:06 CEST). omarchy debug is not available in 4.0.1, so the above was collected by hand.
Filed by Claude Opus 5 via Claude Code.
View raw text
## What happened
Quickshell died with SIGSEGV while Hyprland was shuffling workspaces between monitors during a monitor-profile change. The shell restarted automatically; no user data was lost.
The fault is in the bar's workspaces widget. `plugins/bar/widgets/Workspaces.qml` rebuilds its `Repeater` from inside Quickshell's own mid-mutation `valuesChanged` emit, and its delegates hold live `HyprlandWorkspace` QObject references.
## Symbolized stack
Arch debuginfod, Quickshell 0.3.1, Qt 6.11.2. `si_addr = 0x3e8001ad392` (garbage pointer, not null).
```
#5 QV4::Value::as<QV4::QObjectWrapper> qv4heap_p.h:46 <-- SEGV
#7 QV4::WeakValue::free qv4persistent.cpp:455
#10 QQmlData::destroyed qqmlengine.cpp:1427
#11 QObject::~QObject qobject.cpp:1084
#12 QQmlDMListAccessorData::~QQmlDMListAccessorData qqmldmlistaccessordata.cpp:25
#13 QQmlDelegateModelPrivate::release qqmldelegatemodel.cpp:633
#14 QQuickRepeater::clear qquickrepeater.cpp:422
#15 QQuickRepeater::setModel qquickrepeater.cpp:179
#17 QQmlPropertyPrivate::write qqmlproperty.cpp:1978
#21 QQmlBinding::update qqmlbinding.cpp:166
#22 QQmlNotifier::emitNotify qqmlnotifier.cpp:75
#24 UntypedObjectModel::valuesChanged moc_model.cpp:195
#25 ObjectModel<HyprlandWorkspace>::insertObject core/model.hpp:93 <-- emit mid-insert
#26 ObjectModel<HyprlandWorkspace>::insertObjectSorted
#27 HyprlandIpc::onEvent ipc/connection.cpp:349
#28 HyprlandIpc::eventSocketReady ipc/connection.cpp:136
#33 QAbstractSocketPrivate::canReadNotification qabstractsocket.cpp:673
```
Frame 27 locals identify the event exactly:
```
name = "7"
existed = false
```
So Quickshell had no workspace 7, created one, and emitted `valuesChanged()` from inside `insertObject` while the model list was still being mutated. That synchronous emit re-enters QML, which tears the whole Repeater down.
## Why the Repeater is torn down at all
`plugins/bar/widgets/Workspaces.qml:51`:
```qml
Repeater {
model: root.workspaceIds()
```
`workspaceIds()` reads `Hyprland.workspaces.values`, so the binding is subscribed to `valuesChanged`, and it returns a **fresh JS array on every evaluation**. Every workspace add/remove therefore assigns a new model and triggers a full `setModel` → `clear()` → destroy-all-delegates cycle — from inside the mid-mutation emit. `GridLayout.columns` (line 45) binds the same signal.
The delegate holds a live QObject (line 55):
```qml
readonly property var workspace: root.workspaceById(modelData)
readonly property bool occupied: workspace !== null && workspace.toplevels.values.length > 0
```
Inference, not proven from the frames: the delegate for `modelData == 7` still held a `QObjectWrapper` for the `HyprlandWorkspace` destroyed by the earlier `destroyworkspace>>7`. Destroying that delegate data inside the emit walks a stale V4 persistent slot, which matches the garbage `si_addr` rather than a null deref. The delegate identity is not recoverable from the available frames.
## Trigger / reproduction
Framework Laptop 13 with two external displays, lid transition causing `hyprmoncfgd` to re-apply the "Desktop Mode" monitor profile while the lock screen was coming up. Quickshell's own log tail immediately before the crash:
```
21:22:02 omarchy lock ... secure=true
21:22:03 hyprmoncfgd: applied profile "Desktop Mode"; monitoradded:eDP-1
destroyworkspace>>7
monitoradded>>eDP-1
moveworkspace>>1,DP-2
destroyworkspace>>8
moveworkspace>>3,DP-2
workspace>>7 <-- recreates ws 7 (existed=false)
moveworkspace>>5,DP-2
moveworkspace>>7,DP-2
monitorremoved>>eDP-1
21:22:06 SIGSEGV
```
Any monitor add/remove that makes Hyprland destroy and recreate workspaces should reach this. It does not require the lock screen — the lock screen only supplied the monitor churn here.
## Not a recent regression
The same crash class was recorded on this machine on 2026-08-16 (`QQuickRepeater::regenerate` / `setModel`) under `quickshell-git 0.3.0.r20.g28771c7` and Qt 6.11.1, before the 2026-08-25 switch to `quickshell 0.3.1`. `Workspaces.qml` is byte-identical to current `basecamp/omarchy` HEAD.
## Relationship to #6952
#6952 is the same *class* — a `Repeater` rebuilt from a model-mutation signal with delegates holding live Quickshell QObjects — but not the same bug. That one is PipeWire-driven and faults on the **incubation** path (`VDMListDelegateDataType::createMissingProperties`, `qqmldmlistaccessordata_p.h:222`, null `QObject*` row). This one is Hyprland-IPC-driven and faults on the **destruction** path (`QQmlDMListAccessorData` dtor → `QV4::WeakValue::free`), in a different file. Filing separately rather than as a comment there, but they likely want the same fix pattern.
## Suggested fix
The codebase already has the defense in two places:
- `plugins/panels/bluetooth/Model.js` `deviceRow()` — primitives-only rows, commented *"delegates never hold Device QObject wrappers … which segfaults quickshell"*, with actions re-resolving the live object via `deviceFor()`.
- `plugins/notifications/Service.qml:189` — `Qt.callLater` to avoid mutating a model while a Repeater is mid-incubation.
`Workspaces.qml` does neither. Projecting the delegate to primitives (`id`, `occupied`, `focused`) and resolving the `HyprlandWorkspace` only at press time would remove the dangling wrapper, and deferring the model reassignment with `Qt.callLater` would get the reassignment out of Quickshell's mid-mutation emit. Keeping the model array stable when the id set is unchanged would also cut most of the teardown churn, since `workspaceIds()` returns `[1..5]` plus extras and usually produces an identical list.
Arguably `ObjectModel::insertObject` emitting `valuesChanged()` before the insert has settled deserves an upstream Quickshell report too.
## System
```
Omarchy 4.0.1-1 (stable channel)
quickshell 0.3.1-1
qt6-declarative 6.11.2-1
qt6-base 6.11.2-2
hyprland 0.56.2-1
kernel 7.1.9-arch1-2
hardware Framework Laptop 13 (AMD Ryzen AI 300 Series)
monitors DP-2 ASUS PG32UCDP 3840x2160@120, DP-7 PanaScope PX329 2560x1440@144, eDP-1 internal
```
Crash report on the reporting machine: `~/.cache/quickshell/crashes/pc12k0ckt` (PID 1758098, 2026-08-26 21:22:06 CEST). `omarchy debug` is not available in 4.0.1, so the above was collected by hand.
---
Filed by Claude Opus 5 via Claude Code.
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.
- Out of scopeDecided by agents · verified · 2026-10-09 09:28 UTC