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-1835GitHub issuePublicDecided· Out of scope

Plugin hot-reload never picks up changed plugin code (stale QML component cache)

Reported by farangkao · filed Aug 15, 2026 · open at source

Decided: Out of scope — decided by agents · verified 4 days ago.

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

Plugin hot-reload never picks up changed plugin code (stale QML component cache)revision 1 · captured 2026-09-28 21:29 UTC via poll

System details

13th Gen Intel(R) Core(TM), AMD Radeon AI PRO R9700

What's wrong?

Title: Plugin hot-reload silently reloads stale code — Qt.clearComponentCache doesn't exist in QML

Note: analysis and report generated with AI (GLM 5-3), but reviewed and tested by a human in the loop (me)

Summary

When a locally installed plugin's files change (e.g. after omarchy plugin update),
the shell notices and logs Local plugin changed, reloading: <id>, and plugin
services/widgets are re-instantiated — but they are re-created from the **old
compiled QML**, so the update has no effect until the whole shell is restarted.

Environment

  • Omarchy 4.0.0-1 (stable channel, 4.0.0.alpha)
  • quickshell 0.3.0 (quickshell-git, revision 28771c7c)
  • Plugin under test: lobo.omaspaces (any third-party bar-widget plugin shows this)

Reproduction

  1. Install a git-managed bar-widget plugin at an older version, with the shell running.
  2. Run omarchy plugin update <id> (or just edit a file under

~/.config/omarchy/plugins/<id>/ — the file watcher triggers the same path).
3. The shell logs Local plugin changed, reloading: <id> and reloads.
4. Observe: the widget still runs the old code. Only omarchy restart shell
applies the update.

Concrete example: after updating lobo.omaspaces to a version that added a
right-click handler on its workspace pills, right-click kept doing nothing
after the automatic reload; omarchy restart shell fixed it.

A minimal proof that edits are ignored by the reload: adding a visible change
(e.g. a marker character in the widget's label) plus a
Component.onCompleted: console.warn(...) line to the plugin's QML triggered
the reloading log line, but the marker never appeared and the log line never
fired — the live instance was re-created from the previous compilation.

Root cause

shell.qml reload path: file change → localPluginReloadTimer (line 61) →
reloadPlugins() (line 739) → finishPluginReload() (line 751):

if (typeof Qt.clearComponentCache === "function") Qt.clearComponentCache()

Qt.clearComponentCache is a C++-only method on QQmlEngine; it is not
exposed to the QML Qt object. Verified live — a quickshell config evaluating
typeof Qt.clearComponentCache prints undefined.

Because the guard is silently always false, the engine's in-memory component
cache is never cleared, and the subsequent Qt.createComponent(url) in
loadPluginWidget() (line 794) returns the cached stale Component for
the same URL. Qt's in-memory component cache is keyed by URL and is not
mtime-invalidated — only clearComponentCache() drops it.

Quickshell's own live-reload can't cover this either: it only watches files
under its config path (-p /usr/share/omarchy/shell), while plugins live in
~/.config/omarchy/plugins, outside that tree — which is why the custom
watcher exists in the first place.

The same stale-cache limitation applies to the shell rescanPlugins IPC
function: it re-runs syncPluginWidgets(), which for an unchanged entry-point
URL explicitly re-registers the existing (old) component
("If the component URL is unchanged, just refresh the metadata in place").

Expected behavior

omarchy plugin update (or any plugin file change) should take effect without
a manual shell restart — or, if that's not technically possible from QML, the
reload path should not silently pretend it succeeded.

Suggested fixes

  1. In finishPluginReload(), detect that the cache cannot be cleared from QML

(typeof Qt.clearComponentCache !== "function") and fall back to a full
shell restart (the omarchy-restart-shell flow) instead of silently
reloading stale code.
2. Simpler alternative: have omarchy-plugin-update / omarchy-plugin-add
invoke omarchy-restart-shell when the shell is running.
3. Long-term: expose engine cache clearing (or a reload command) in quickshell
itself; there is currently no quickshell reload subcommand, and kill +
relaunch is the only way to get a fresh engine.

Workaround

omarchy restart shell after any plugin update. (Note: omarchy refresh shell
also restarts but resets shell.json to defaults — not what you want.)

View raw text
### System details

13th Gen Intel(R) Core(TM), AMD Radeon AI PRO R9700

### What's wrong?

**Title:** Plugin hot-reload silently reloads stale code — `Qt.clearComponentCache` doesn't exist in QML

Note: analysis and report generated with AI (GLM 5-3), but reviewed and tested by a human in the loop (me) 

## Summary
 
When a locally installed plugin's files change (e.g. after `omarchy plugin update`),
the shell notices and logs `Local plugin changed, reloading: <id>`, and plugin
services/widgets are re-instantiated — but they are re-created from the **old
compiled QML**, so the update has no effect until the whole shell is restarted.
 
## Environment
 
- Omarchy 4.0.0-1 (stable channel, `4.0.0.alpha`)
- quickshell 0.3.0 (quickshell-git, revision 28771c7c)
- Plugin under test: `lobo.omaspaces` (any third-party bar-widget plugin shows this)
 
## Reproduction
 
1. Install a git-managed bar-widget plugin at an older version, with the shell running.
2. Run `omarchy plugin update <id>` (or just edit a file under
   `~/.config/omarchy/plugins/<id>/` — the file watcher triggers the same path).
3. The shell logs `Local plugin changed, reloading: <id>` and reloads.
4. Observe: the widget still runs the old code. Only `omarchy restart shell`
   applies the update.
 
Concrete example: after updating `lobo.omaspaces` to a version that added a
right-click handler on its workspace pills, right-click kept doing nothing
after the automatic reload; `omarchy restart shell` fixed it.
 
A minimal proof that edits are ignored by the reload: adding a visible change
(e.g. a marker character in the widget's label) plus a
`Component.onCompleted: console.warn(...)` line to the plugin's QML triggered
the `reloading` log line, but the marker never appeared and the log line never
fired — the live instance was re-created from the previous compilation.
 
## Root cause
 
`shell.qml` reload path: file change → `localPluginReloadTimer` (line 61) →
`reloadPlugins()` (line 739) → `finishPluginReload()` (line 751):
 
```qml
if (typeof Qt.clearComponentCache === "function") Qt.clearComponentCache()
```
 
`Qt.clearComponentCache` is a C++-only method on `QQmlEngine`; it is not
exposed to the QML `Qt` object. Verified live — a quickshell config evaluating
`typeof Qt.clearComponentCache` prints `undefined`.
 
Because the guard is silently always false, the engine's in-memory component
cache is never cleared, and the subsequent `Qt.createComponent(url)` in
`loadPluginWidget()` (line 794) returns the cached **stale** `Component` for
the same URL. Qt's in-memory component cache is keyed by URL and is not
mtime-invalidated — only `clearComponentCache()` drops it.
 
Quickshell's own live-reload can't cover this either: it only watches files
under its config path (`-p /usr/share/omarchy/shell`), while plugins live in
`~/.config/omarchy/plugins`, outside that tree — which is why the custom
watcher exists in the first place.
 
The same stale-cache limitation applies to the `shell rescanPlugins` IPC
function: it re-runs `syncPluginWidgets()`, which for an unchanged entry-point
URL explicitly re-registers the existing (old) component
("If the component URL is unchanged, just refresh the metadata in place").
 
## Expected behavior
 
`omarchy plugin update` (or any plugin file change) should take effect without
a manual shell restart — or, if that's not technically possible from QML, the
reload path should not silently pretend it succeeded.
 
## Suggested fixes
 
1. In `finishPluginReload()`, detect that the cache cannot be cleared from QML
   (`typeof Qt.clearComponentCache !== "function"`) and fall back to a full
   shell restart (the `omarchy-restart-shell` flow) instead of silently
   reloading stale code.
2. Simpler alternative: have `omarchy-plugin-update` / `omarchy-plugin-add`
   invoke `omarchy-restart-shell` when the shell is running.
3. Long-term: expose engine cache clearing (or a reload command) in quickshell
   itself; there is currently no `quickshell reload` subcommand, and `kill` +
   relaunch is the only way to get a fresh engine.
 
## Workaround
 
`omarchy restart shell` after any plugin update. (Note: `omarchy refresh shell`
also restarts but resets shell.json to defaults — not what you want.)
 

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 04:10 UTC