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.
Regression (4.0.2→4.0.3): publicPluginManifest() strips __sourceDir, breaking third-party plugins that locate their own files
Reported by redglover · filed Sep 8, 2026Closed upstream on 2026-10-03 by the merge of PR #11925 (closed by spencerbull). 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
Upgrading from Omarchy 4.0.2 to 4.0.3 broke the third-party Omarchy Spotify
plugin (quickshell.spotify). Every helper script the plugin spawns now runs
with a broken path, e.g.:
omarchy-shell[1314]: WARN: Process failed to start, likely because the binary
could not be found. Command: QList("/scripts/keyring-store.sh", "d420a117…")The path should have been/home/<user>/.config/omarchy/plugins/quickshell.spotify/scripts/keyring-store.sh.
The /…plugins/quickshell.spotify prefix is gone — the plugin resolved it frommanifest.__sourceDir, which is now empty.
User-visible effects for that plugin: fresh login broken (pkce.sh), OAuth
refresh-token persistence broken (keyring-store.sh — silently drops the
rotated token, so the session dies on next restart), local playback broken
(configure-spotifyd.sh, playback-runtime.sh), and Spotify Connect device
control broken (spotify-connect-device.py).
Root cause
4.0.3 added publicPluginManifest() in /usr/share/omarchy/shell/shell.qml
and routed the plugin-facing manifest through it:
// shell.qml (4.0.3-1), lines 316-324
function publicPluginManifest(manifest) {
if (!manifest) return null
if (manifest.__isFirstParty) return manifest
var copy = JSON.parse(JSON.stringify(manifest))
delete copy.__sourceDir // <-- line 320
delete copy.__isFirstParty
delete copy.__hostCapabilities
return copy
}It is now applied at every point where a third-party plugin receives its
manifest — services (line 930), scoped registry API (754), bar config (222),
panels (1343), rescans (977, 983), etc.
In 4.0.2 the raw manifest, including __sourceDir, was handed straight to the
plugin:
// shell.qml (4.0.2-1), line 307
if ("manifest" in inst) inst.manifest = manifestSo under 4.0.2 a third-party plugin could read manifest.__sourceDir to find
its own directory (needed to exec bundled scripts, read bundled assets, etc.).
Under 4.0.3 there is no supported way for a non-first-party plugin to learn
its own install path:
manifest.__sourceDir— deleted bypublicPluginManifest()pluginRegistry.installedPlugins[id](viaPluginRegistryApi.qml) — returns
the same sanitized manifest
- entryPointUrl() — only resolves declared entry points, and
PluginRegistryApi.entryPointUrl() requires passing back a manifest whose
id matches, but gives no directory
Note shell.qml:1410 still stamps the real __sourceDir into the **bar
widget** metadata (meta.sourceDir), so the information is available to the
host — it is just withheld from the plugin's service/panel components, which is
where scripts actually get spawned.
Expected
A plugin should have a supported, documented way to resolve its own install
directory. Options:
- Keep exposing
__sourceDir(or a renamedsourceDir) on the plugin-facing
manifest — it is not sensitive; it is always
~/.config/omarchy/plugins/<id> (or the first-party dir), which the plugin
can already guess.
2. Or add it to the shell API handed to plugins (e.g. shell.pluginDir /
shell.sourceDir), the way meta.sourceDir is already passed to widgets.
3. Or document Qt.resolvedUrl(".") from a component file as the blessed
approach and note the manifest no longer carries a path.
If the removal was a deliberate security boundary, a note in the 4.0.3
migration/changelog plus a plugin-API doc update would have caught this before
release. Third-party plugins other than Omarchy Spotify that bundle scripts or
assets are likely affected too.
Related: #10661 is the same class of bug — third-party plugin manifests being
mangled before the plugin sees them (there, array types lost through the panel
Instantiator).
Steps to reproduce
- On Omarchy 4.0.2, install a third-party plugin that reads
manifest.__sourceDir (e.g. `omarchy plugin add
https://github.com/stappmus/Omarchy-Spotify.git --enable`) and confirm it
works.
2. omarchy update to 4.0.3, omarchy restart shell.
3. Open the plugin. Its bundled scripts fail with "binary could not be found"
and paths like /scripts/foo.sh.
Workaround (plugin side)
Patch the plugin to fall back to Qt.resolvedUrl(".") when __sourceDir is
absent. Filed separately against the plugin:
https://github.com/stappmus/Omarchy-Spotify/issues/69
System details
- Omarchy: 4.0.2-1 → 4.0.3-1 (regression introduced by the upgrade)
- Kernel: 6.x (Arch), x86_64
- CPU: Intel Core i7-9750H
- GPU: Intel UHD 630 + NVIDIA GTX 1660 Ti Mobile
- quickshell 0.3.1-1, hyprland 0.56.2-2
- Plugin: stappmus/Omarchy-Spotify @ e6c9f9f (manifest version 1.0.4)
View raw text
## What happened
Upgrading from Omarchy 4.0.2 to 4.0.3 broke the third-party **Omarchy Spotify**
plugin (`quickshell.spotify`). Every helper script the plugin spawns now runs
with a broken path, e.g.:
```
omarchy-shell[1314]: WARN: Process failed to start, likely because the binary
could not be found. Command: QList("/scripts/keyring-store.sh", "d420a117…")
```
The path should have been
`/home/<user>/.config/omarchy/plugins/quickshell.spotify/scripts/keyring-store.sh`.
The `/…plugins/quickshell.spotify` prefix is gone — the plugin resolved it from
`manifest.__sourceDir`, which is now empty.
User-visible effects for that plugin: fresh login broken (`pkce.sh`), OAuth
refresh-token persistence broken (`keyring-store.sh` — silently drops the
rotated token, so the session dies on next restart), local playback broken
(`configure-spotifyd.sh`, `playback-runtime.sh`), and Spotify Connect device
control broken (`spotify-connect-device.py`).
## Root cause
4.0.3 added `publicPluginManifest()` in `/usr/share/omarchy/shell/shell.qml`
and routed the plugin-facing manifest through it:
```qml
// shell.qml (4.0.3-1), lines 316-324
function publicPluginManifest(manifest) {
if (!manifest) return null
if (manifest.__isFirstParty) return manifest
var copy = JSON.parse(JSON.stringify(manifest))
delete copy.__sourceDir // <-- line 320
delete copy.__isFirstParty
delete copy.__hostCapabilities
return copy
}
```
It is now applied at every point where a third-party plugin receives its
manifest — services (line 930), scoped registry API (754), bar config (222),
panels (1343), rescans (977, 983), etc.
In 4.0.2 the raw manifest, including `__sourceDir`, was handed straight to the
plugin:
```qml
// shell.qml (4.0.2-1), line 307
if ("manifest" in inst) inst.manifest = manifest
```
So under 4.0.2 a third-party plugin could read `manifest.__sourceDir` to find
its own directory (needed to exec bundled scripts, read bundled assets, etc.).
Under 4.0.3 there is **no supported way** for a non-first-party plugin to learn
its own install path:
- `manifest.__sourceDir` — deleted by `publicPluginManifest()`
- `pluginRegistry.installedPlugins[id]` (via `PluginRegistryApi.qml`) — returns
the same sanitized manifest
- `entryPointUrl()` — only resolves declared entry points, and
`PluginRegistryApi.entryPointUrl()` requires passing back a manifest whose
`id` matches, but gives no directory
Note `shell.qml:1410` still stamps the real `__sourceDir` into the **bar
widget** metadata (`meta.sourceDir`), so the information is available to the
host — it is just withheld from the plugin's service/panel components, which is
where scripts actually get spawned.
## Expected
A plugin should have a supported, documented way to resolve its own install
directory. Options:
1. Keep exposing `__sourceDir` (or a renamed `sourceDir`) on the plugin-facing
manifest — it is not sensitive; it is always
`~/.config/omarchy/plugins/<id>` (or the first-party dir), which the plugin
can already guess.
2. Or add it to the `shell` API handed to plugins (e.g. `shell.pluginDir` /
`shell.sourceDir`), the way `meta.sourceDir` is already passed to widgets.
3. Or document `Qt.resolvedUrl(".")` from a component file as the blessed
approach and note the manifest no longer carries a path.
If the removal was a deliberate security boundary, a note in the 4.0.3
migration/changelog plus a plugin-API doc update would have caught this before
release. Third-party plugins other than Omarchy Spotify that bundle scripts or
assets are likely affected too.
Related: #10661 is the same class of bug — third-party plugin manifests being
mangled before the plugin sees them (there, array types lost through the panel
Instantiator).
## Steps to reproduce
1. On Omarchy 4.0.2, install a third-party plugin that reads
`manifest.__sourceDir` (e.g. `omarchy plugin add
https://github.com/stappmus/Omarchy-Spotify.git --enable`) and confirm it
works.
2. `omarchy update` to 4.0.3, `omarchy restart shell`.
3. Open the plugin. Its bundled scripts fail with "binary could not be found"
and paths like `/scripts/foo.sh`.
## Workaround (plugin side)
Patch the plugin to fall back to `Qt.resolvedUrl(".")` when `__sourceDir` is
absent. Filed separately against the plugin:
https://github.com/stappmus/Omarchy-Spotify/issues/69
## System details
- Omarchy: 4.0.2-1 → **4.0.3-1** (regression introduced by the upgrade)
- Kernel: 6.x (Arch), x86_64
- CPU: Intel Core i7-9750H
- GPU: Intel UHD 630 + NVIDIA GTX 1660 Ti Mobile
- quickshell 0.3.1-1, hyprland 0.56.2-2
- Plugin: stappmus/Omarchy-Spotify @ e6c9f9f (manifest version 1.0.4)
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.