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.
No way to disable the idle lock while keeping the screensaver — and `idle.lock: 0` locks instantly instead of disabling
Reported by Alex980102 · filed Aug 16, 2026Closed upstream on 2026-10-04 by the merge of PR #12538 (closed by dhh). 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
Summary
There is no way to keep the screensaver while disabling the idle lock, and the value a user would naturally reach for to express that — idle.lock: 0 — does the exact opposite: it locks immediately on idle.
Two asks in one report, because they are the same gap seen from both sides:
- Feature — give
idle.locka real "never lock" value. - Bug / footgun —
0,nullandfalseall currently mean "lock instantly", which is the opposite of how every one of them reads.
Use case
Desktop that runs long jobs unattended. The user wants the screensaver to kick in, but never wants to come back to a password prompt, and never wants the machine to suspend. Screensaver and lock are separate concepts in the shell (omarchy-launch-screensaver vs omarchy-system-lock), so wanting one without the other is coherent — there is just no way to express it.
The footgun
shell/plugins/services/idle/Service.qml:
readonly property int lockTimeoutSeconds: secondsFromConfig(idleConfig.lock, defaultLockSeconds)
readonly property int firstIdleTimeoutSeconds: Math.min(screensaverTimeoutSeconds, lockTimeoutSeconds)
readonly property int lockDelaySeconds: Math.max(0, lockTimeoutSeconds - firstIdleTimeoutSeconds)
...
if (root.lockDelaySeconds === 0) lockSystem("lock-timeout-immediate")With "lock": 0, firstIdleTimeoutSeconds becomes min(screensaver, 0) = 0 and lockDelaySeconds is 0, so startIdleCycle() calls lockSystem("lock-timeout-immediate") the moment idle is detected. The IdleMonitor timeout also collapses to 0.
The other two intuitive spellings fail the same way, via shell/plugins/services/idle/IdleModel.js:
function secondsFromConfig(value, fallback) {
var n = Number(value)
if (!isFinite(n) || n < 0) return fallback
return Math.floor(n)
}Number(null) and Number(false) are both 0 — finite and not negative — so they sail through the guard and land on the immediate-lock path. And a negative value is rejected into the 300s fallback, so there is no escape hatch there either.
Net result: of the four values a user would try to mean "off" (0, -1, null, false), three lock instantly and one silently keeps the default.
Current workaround, and why it is not a fix
Set idle.lock to a value large enough that the timer never fires:
{ "idle": { "screensaver": 150, "lock": 604800 } }This works — the lock timer is armed for ~7 days and the cycle is cancelled the moment the screensaver is dismissed via handleScreensaverWindowClosed → cancelIdleCycle. But it is a workaround for three reasons:
- It relies on an undocumented ceiling.
Timer.intervalis a 32-bit int in milliseconds, solockDelaySecondsmust stay under ~2,147,483 (≈24.8 days) or it overflows. Nothing inshell/README.mdtells a user this, and the natural instinct (99999999) is past the limit. - It does not express intent. A reader of
shell.jsonsees a magic number, not "locking is off". - `shell/README.md` §7 documents `idle.lock` as seconds only. There is no supported spelling for the behaviour, so anyone who wants it has to read the QML.
Suggested fix
Treat an explicit false (and/or null) as "never lock", and either accept 0 as the same thing or reject it loudly rather than treating it as instant:
readonly property bool lockEnabled: idleConfig.lock !== false
&& idleConfig.lock !== null
&& lockTimeoutSeconds > 0then guard the two lockSystem call sites (the lockDelaySeconds === 0 branch in startIdleCycle, and lockTimer.onTriggered) on lockEnabled, and skip arming lockTimer when it is false.
If instant-lock-on-idle is a behaviour someone actually relies on, it deserves an explicit spelling ("lock": "immediate") rather than being what you get for typing the number that reads as "off".
Whatever is chosen, shell/README.md §7 should state it, since that section is the only place the idle schema is documented.
Environment
- Omarchy 4.0.0.alpha
- Config:
~/.config/omarchy/shell.json→idle.screensaver: 150,idle.lock: 300(default) - Verified against the running shell with
omarchy-shell idle status
View raw text
## Summary
There is no way to keep the screensaver while disabling the idle lock, and the value a user would naturally reach for to express that — `idle.lock: 0` — does the exact opposite: it locks **immediately** on idle.
Two asks in one report, because they are the same gap seen from both sides:
1. **Feature** — give `idle.lock` a real "never lock" value.
2. **Bug / footgun** — `0`, `null` and `false` all currently mean "lock instantly", which is the opposite of how every one of them reads.
## Use case
Desktop that runs long jobs unattended. The user wants the screensaver to kick in, but never wants to come back to a password prompt, and never wants the machine to suspend. Screensaver and lock are separate concepts in the shell (`omarchy-launch-screensaver` vs `omarchy-system-lock`), so wanting one without the other is coherent — there is just no way to express it.
## The footgun
`shell/plugins/services/idle/Service.qml`:
```qml
readonly property int lockTimeoutSeconds: secondsFromConfig(idleConfig.lock, defaultLockSeconds)
readonly property int firstIdleTimeoutSeconds: Math.min(screensaverTimeoutSeconds, lockTimeoutSeconds)
readonly property int lockDelaySeconds: Math.max(0, lockTimeoutSeconds - firstIdleTimeoutSeconds)
...
if (root.lockDelaySeconds === 0) lockSystem("lock-timeout-immediate")
```
With `"lock": 0`, `firstIdleTimeoutSeconds` becomes `min(screensaver, 0) = 0` and `lockDelaySeconds` is `0`, so `startIdleCycle()` calls `lockSystem("lock-timeout-immediate")` the moment idle is detected. The `IdleMonitor` timeout also collapses to `0`.
The other two intuitive spellings fail the same way, via `shell/plugins/services/idle/IdleModel.js`:
```js
function secondsFromConfig(value, fallback) {
var n = Number(value)
if (!isFinite(n) || n < 0) return fallback
return Math.floor(n)
}
```
`Number(null)` and `Number(false)` are both `0` — finite and not negative — so they sail through the guard and land on the immediate-lock path. And a negative value is rejected into the 300s fallback, so there is no escape hatch there either.
Net result: of the four values a user would try to mean "off" (`0`, `-1`, `null`, `false`), three lock instantly and one silently keeps the default.
## Current workaround, and why it is not a fix
Set `idle.lock` to a value large enough that the timer never fires:
```json
{ "idle": { "screensaver": 150, "lock": 604800 } }
```
This works — the lock timer is armed for ~7 days and the cycle is cancelled the moment the screensaver is dismissed via `handleScreensaverWindowClosed` → `cancelIdleCycle`. But it is a workaround for three reasons:
- **It relies on an undocumented ceiling.** `Timer.interval` is a 32-bit int in milliseconds, so `lockDelaySeconds` must stay under ~2,147,483 (≈24.8 days) or it overflows. Nothing in `shell/README.md` tells a user this, and the natural instinct (`99999999`) is past the limit.
- **It does not express intent.** A reader of `shell.json` sees a magic number, not "locking is off".
- **`shell/README.md` §7 documents `idle.lock` as seconds only.** There is no supported spelling for the behaviour, so anyone who wants it has to read the QML.
## Suggested fix
Treat an explicit `false` (and/or `null`) as "never lock", and either accept `0` as the same thing or reject it loudly rather than treating it as instant:
```qml
readonly property bool lockEnabled: idleConfig.lock !== false
&& idleConfig.lock !== null
&& lockTimeoutSeconds > 0
```
then guard the two `lockSystem` call sites (the `lockDelaySeconds === 0` branch in `startIdleCycle`, and `lockTimer.onTriggered`) on `lockEnabled`, and skip arming `lockTimer` when it is false.
If instant-lock-on-idle is a behaviour someone actually relies on, it deserves an explicit spelling (`"lock": "immediate"`) rather than being what you get for typing the number that reads as "off".
Whatever is chosen, `shell/README.md` §7 should state it, since that section is the only place the idle schema is documented.
## Environment
- Omarchy 4.0.0.alpha
- Config: `~/.config/omarchy/shell.json` → `idle.screensaver: 150`, `idle.lock: 300` (default)
- Verified against the running shell with `omarchy-shell idle status`
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
- Related PRpr/7863· confirmed
- Related PRpr/9170· confirmed
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.