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.
Audio panel segfaults Quickshell when its cached sink/source list holds destroyed PwNodes
Reported by chava-byte · filed Oct 3, 2026Closed upstream on 2026-10-04 by the bot omarchybot; the closing comment says it was fixed by PR #7783. GitHub close reason: completed.
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
Opening the audio panel crashed the Omarchy shell (SIGSEGV). Quickshell's crash handler restarted it in place.
Mechanism
shell/plugins/panels/audio/Panel.qml stores live PwNode objects in cachedAudioSinks / cachedAudioSources (lines 76-77, updated at 151-152), and audioSinks / audioSources fall back to those caches when rawAudioSinks / rawAudioSources is empty (lines 93-94).
When WirePlumber restarts (or a device goes away), those PwNodes are destroyed but stay in the cache as dead wrappers. If no replacement sink/source shows up, the live list is empty and the panel uses the stale cache. Opening the panel then runs:
onOpenedChanged → refreshDisplayAudioModels() → displayAudio* = listSnapshot(...) → Repeater { model: root.displayAudio* } rebuilds with a null QObject.
While the delegate incubates, the non-library Model.js is re-evaluated in the delegate context, and its top-level typeof module lookup goes through the delegate's model object. Qt's JS-array model adaptor (VDMListDelegateDataType, libQt6QmlModels) then calls a virtual method on the null QObject* (mov (%rax),%rdx with rax = 0).
This is the same class of bug that the bluetooth and network panels already work around by building primitives-only rows (see the comments on deviceRow() and wifiRow() in their Model.js).
In this session WirePlumber had been restarted 5 times, and afterwards the machine had no hardware sinks or sources at all (only the omarchy_speaker_tuning filter sink), so the fallback to the stale cache was taken.
Stack (abridged; Qt frames unsymbolized because Arch debuginfod has no debuginfo for these qt6-declarative build IDs)
#4 libQt6QmlModels.so.6 + 0xb3530 <- SIGSEGV, null QObject* virtual call
#5 libQt6QmlModels.so.6 + 0xb3f01
#6 libQt6Qml.so.6 + 0x3880a7
#7 QV4::QObjectWrapper::getQmlProperty
#8 QV4::QQmlContextWrapper::getPropertyAndBase
#10 QV4::Runtime::TypeofName::call
#13 QV4::Module::evaluate
#15 QQmlEnginePrivate::createInternalContext
#19 QQmlIncubatorPrivate::incubate
#22 QQuickRepeater::regenerate
#23 QQuickRepeater::setModel
#29 QQmlBinding::update
#36 QV4::Runtime::StoreNameSloppy::call <- displayAudio* = ...
#40 QV4::Runtime::CallQmlContextPropertyLookup::call <- refreshDisplayAudioModels()
#46 QQmlBoundSignalExpression::evaluate <- onOpenedChanged
...
#106 QQuickMouseArea::mouseReleaseEventNote: I matched the QML side of the stack to the audio panel from the frame shape, the code, and the system state, not from a symbolized QML filename. Every step lines up with this path.
Expected
Opening the audio panel never crashes, even after audio devices disappear.
Steps to reproduce (expected)
- Have at least one hardware audio source/sink so the cache gets filled.
- Restart WirePlumber, or remove the device, so that no hardware source/sink comes back.
- Open the audio panel.
Suggested fix
Do what bluetooth and network already do: give the Repeaters primitive rows and resolve the PwNode on demand. Alternatively, filter cachedAudioSinks / cachedAudioSources against Pipewire.nodes.values before using them, or don't cache node objects at all.
System
- Omarchy 4.0.4-1, kernel 7.2.5-3-omarchy
- Dell XPS 14 DA14260 (Intel Xe2 graphics)
- quickshell 0.3.1-1, qt6-declarative 6.11.2-1, qt6-base 6.11.2-3
- pipewire 1.6.8-1, wireplumber 0.5.17-1, hyprland 0.56.2-2
(omarchy debug isn't available in 4.0.4, so I collected these details by hand.)
Filed by Claude Opus 5.5 via Claude Code.
View raw text
## What happened
Opening the audio panel crashed the Omarchy shell (SIGSEGV). Quickshell's crash handler restarted it in place.
## Mechanism
`shell/plugins/panels/audio/Panel.qml` stores live `PwNode` objects in `cachedAudioSinks` / `cachedAudioSources` (lines 76-77, updated at 151-152), and `audioSinks` / `audioSources` fall back to those caches when `rawAudioSinks` / `rawAudioSources` is empty (lines 93-94).
When WirePlumber restarts (or a device goes away), those `PwNode`s are destroyed but stay in the cache as dead wrappers. If no replacement sink/source shows up, the live list is empty and the panel uses the stale cache. Opening the panel then runs:
`onOpenedChanged` → `refreshDisplayAudioModels()` → `displayAudio* = listSnapshot(...)` → `Repeater { model: root.displayAudio* }` rebuilds with a null QObject.
While the delegate incubates, the non-library `Model.js` is re-evaluated in the delegate context, and its top-level `typeof module` lookup goes through the delegate's model object. Qt's JS-array model adaptor (`VDMListDelegateDataType`, libQt6QmlModels) then calls a virtual method on the null QObject* (`mov (%rax),%rdx` with `rax = 0`).
This is the same class of bug that the bluetooth and network panels already work around by building primitives-only rows (see the comments on `deviceRow()` and `wifiRow()` in their `Model.js`).
In this session WirePlumber had been restarted 5 times, and afterwards the machine had no hardware sinks or sources at all (only the `omarchy_speaker_tuning` filter sink), so the fallback to the stale cache was taken.
## Stack (abridged; Qt frames unsymbolized because Arch debuginfod has no debuginfo for these qt6-declarative build IDs)
```
#4 libQt6QmlModels.so.6 + 0xb3530 <- SIGSEGV, null QObject* virtual call
#5 libQt6QmlModels.so.6 + 0xb3f01
#6 libQt6Qml.so.6 + 0x3880a7
#7 QV4::QObjectWrapper::getQmlProperty
#8 QV4::QQmlContextWrapper::getPropertyAndBase
#10 QV4::Runtime::TypeofName::call
#13 QV4::Module::evaluate
#15 QQmlEnginePrivate::createInternalContext
#19 QQmlIncubatorPrivate::incubate
#22 QQuickRepeater::regenerate
#23 QQuickRepeater::setModel
#29 QQmlBinding::update
#36 QV4::Runtime::StoreNameSloppy::call <- displayAudio* = ...
#40 QV4::Runtime::CallQmlContextPropertyLookup::call <- refreshDisplayAudioModels()
#46 QQmlBoundSignalExpression::evaluate <- onOpenedChanged
...
#106 QQuickMouseArea::mouseReleaseEvent
```
Note: I matched the QML side of the stack to the audio panel from the frame shape, the code, and the system state, not from a symbolized QML filename. Every step lines up with this path.
## Expected
Opening the audio panel never crashes, even after audio devices disappear.
## Steps to reproduce (expected)
1. Have at least one hardware audio source/sink so the cache gets filled.
2. Restart WirePlumber, or remove the device, so that no hardware source/sink comes back.
3. Open the audio panel.
## Suggested fix
Do what bluetooth and network already do: give the Repeaters primitive rows and resolve the `PwNode` on demand. Alternatively, filter `cachedAudioSinks` / `cachedAudioSources` against `Pipewire.nodes.values` before using them, or don't cache node objects at all.
## System
- Omarchy 4.0.4-1, kernel 7.2.5-3-omarchy
- Dell XPS 14 DA14260 (Intel Xe2 graphics)
- quickshell 0.3.1-1, qt6-declarative 6.11.2-1, qt6-base 6.11.2-3
- pipewire 1.6.8-1, wireplumber 0.5.17-1, hyprland 0.56.2-2
(`omarchy debug` isn't available in 4.0.4, so I collected these details by hand.)
---
Filed by Claude Opus 5.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.
No decision has been recorded yet.