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.
Lock screen fingerprint retry livelocks after resume: overlapping PAM subprocesses collide on exclusive fprintd claim
Reported by sross · filed Aug 16, 2026Closed upstream on 2026-10-04 by the merge of PR #7158 (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
What happens
On a laptop with a fingerprint reader, unlocking by fingerprint intermittently stops working entirely. The lock screen shows no fingerprint progress and only a typed password gets you in.
Under the hood the lock screen enters a self-sustaining retry livelock: it spawns 3–5 overlapping pam.subprocess instances per second, every one failing to claim the fingerprint device, until the user gives up and types their password.
Captured from a real failure (13 seconds, 41 attempts, all failing):
19:20:23 Lid closed -> PM: suspend entry (s2idle)
19:20:24 PM: suspend exit -> Lid opened (1-second suspend)
19:20:24 omarchy-shell[182126]: Starting pam session ... "omarchy-lock-fingerprint"
19:20:24 omarchy-shell[182167]: Starting pam session ... "omarchy-lock-fingerprint"
19:20:24 omarchy-shell[182170]: Starting pam session ... "omarchy-lock-fingerprint"
... 41 sessions in 13s, each:
Error while authenticating: "Authentication service cannot
retrieve authentication info" (code 9) [PAM_AUTHINFO_UNAVAIL]
19:20:36 falls back to "omarchy-lock-password" -> user types passwordConcurrent distinct PAM subprocess PIDs per second across that window: 5, 3, 3, 4, 3, 3, 3, 4, 3, 3, 4, 4, 3.
A healthy locked session sits at ~2 attempts/minute by comparison. Over one day: 116 fingerprint PAM sessions vs 5 password fallbacks.
Root cause
shell/plugins/lock/Service.qml:209-228:
function startFingerprint() {
if (!lockRequested || !sessionLock.secure || !fingerprintConfigured) return
if (fingerprintPam.active || fingerprintAuthenticating) return // :211
fingerprintAuthenticating = true
...
}
function handleFingerprintFinished(result) {
fingerprintAuthenticating = false // :220 — cleared on RESULT
if (!lockRequested) return
if (result === PamResult.Success) {
finishUnlock()
} else if (fingerprintConfigured) {
fingerprintRetryTimer.restart() // :226 — fires 250ms later
}
}with
Timer {
id: fingerprintRetryTimer
interval: 250 // :358 — flat, no backoff
repeat: false
onTriggered: root.startFingerprint()
}Both guards on line 211 clear as soon as PAM reports a result. Neither tracks whether the forked pam.subprocess has actually exited and released its claim on the fprintd device.
fprintd devices are exclusive-claim. Subprocess teardown takes longer than the 250 ms retry interval, so the retry starts a fresh subprocess that tries to claim a device the previous one still holds. That claim fails, which immediately calls handleFingerprintFinished, which restarts the timer — now with two subprocesses in teardown. The loop feeds itself and never drains.
The failure mode is confirmed directly against fprintd. Running fprintd-verify manually at the moment of the storm returns the underlying error the PAM code 9 is masking:
$ fprintd-verify
Using device /net/reactivated/Fprint/Device/0
failed to claim device: GDBus.Error:net.reactivated.Fprint.Error.Internal:
Open failed with error: The device is still busy with another operation,
please try again later.The same command before suspend and again 10 s later both succeed with verify-match, exit 0 — so the reader itself is healthy throughout.
The livelock needs one seed failure to enter. Resume from suspend supplies it: the device is genuinely unavailable for a moment as USB re-enumerates (usb 3-7: reset full-speed USB device number 3 using xhci_hcd on every resume). A clean lock succeeds on the first attempt and never enters the loop, which is why this presents as random.
Expected
A transient claim failure should back off and retry, not spin at 4 Hz spawning overlapping subprocesses that guarantee further claim failures.
Steps to reproduce
- Laptop with an enrolled fingerprint and
omarchy-lock-fingerprintin use. - Close the lid and reopen it within about a second, so the machine suspends and resumes immediately.
- Try to unlock by fingerprint.
- Watch
journalctl -f | grep -E 'omarchy-shell\[|code 9'— observe multiple concurrent PAM subprocesses per second, all returning code 9.
Not 100% reproducible per attempt, since it depends on catching the device mid-re-enumeration. Roughly 2 storms per day in normal use here.
Suggested fix
Three things, any of which breaks the loop:
- Give
fingerprintRetryTimerexponential backoff (e.g. 250 ms → 2 s ceiling) instead of a flat 250 ms. - Gate
startFingerprint()on actual subprocess exit rather than on the PAM result, so a still-releasing claim can't be raced. - Distinguish a claim failure (device busy/unavailable — retry with backoff) from a failed match (finger rejected — normal retry cadence). Right now both funnel through the same path.
System details
- Omarchy 4.0.0-1
- Kernel 7.1.8-arch1-3
- Intel Core Ultra X7 358H
- Reader: Goodix MOC
27c6:609c(Framework Laptop) - fprintd 1.94.5-2, libfprint 1.94.100-1
- Suspend type: s2idle
Note: USB autosuspend was ruled out as a factor — the reader was pinned to power/control=on during testing and the livelock still occurred.
View raw text
## What happens
On a laptop with a fingerprint reader, unlocking by fingerprint intermittently stops working entirely. The lock screen shows no fingerprint progress and only a typed password gets you in.
Under the hood the lock screen enters a self-sustaining retry livelock: it spawns 3–5 overlapping `pam.subprocess` instances per second, every one failing to claim the fingerprint device, until the user gives up and types their password.
Captured from a real failure (13 seconds, 41 attempts, all failing):
```
19:20:23 Lid closed -> PM: suspend entry (s2idle)
19:20:24 PM: suspend exit -> Lid opened (1-second suspend)
19:20:24 omarchy-shell[182126]: Starting pam session ... "omarchy-lock-fingerprint"
19:20:24 omarchy-shell[182167]: Starting pam session ... "omarchy-lock-fingerprint"
19:20:24 omarchy-shell[182170]: Starting pam session ... "omarchy-lock-fingerprint"
... 41 sessions in 13s, each:
Error while authenticating: "Authentication service cannot
retrieve authentication info" (code 9) [PAM_AUTHINFO_UNAVAIL]
19:20:36 falls back to "omarchy-lock-password" -> user types password
```
Concurrent distinct PAM subprocess PIDs per second across that window: 5, 3, 3, 4, 3, 3, 3, 4, 3, 3, 4, 4, 3.
A healthy locked session sits at ~2 attempts/minute by comparison. Over one day: 116 fingerprint PAM sessions vs 5 password fallbacks.
## Root cause
`shell/plugins/lock/Service.qml:209-228`:
```qml
function startFingerprint() {
if (!lockRequested || !sessionLock.secure || !fingerprintConfigured) return
if (fingerprintPam.active || fingerprintAuthenticating) return // :211
fingerprintAuthenticating = true
...
}
function handleFingerprintFinished(result) {
fingerprintAuthenticating = false // :220 — cleared on RESULT
if (!lockRequested) return
if (result === PamResult.Success) {
finishUnlock()
} else if (fingerprintConfigured) {
fingerprintRetryTimer.restart() // :226 — fires 250ms later
}
}
```
with
```qml
Timer {
id: fingerprintRetryTimer
interval: 250 // :358 — flat, no backoff
repeat: false
onTriggered: root.startFingerprint()
}
```
Both guards on line 211 clear as soon as PAM *reports a result*. Neither tracks whether the forked `pam.subprocess` has actually exited and released its claim on the fprintd device.
fprintd devices are exclusive-claim. Subprocess teardown takes longer than the 250 ms retry interval, so the retry starts a fresh subprocess that tries to claim a device the previous one still holds. That claim fails, which immediately calls `handleFingerprintFinished`, which restarts the timer — now with two subprocesses in teardown. The loop feeds itself and never drains.
The failure mode is confirmed directly against fprintd. Running `fprintd-verify` manually at the moment of the storm returns the underlying error the PAM code 9 is masking:
```
$ fprintd-verify
Using device /net/reactivated/Fprint/Device/0
failed to claim device: GDBus.Error:net.reactivated.Fprint.Error.Internal:
Open failed with error: The device is still busy with another operation,
please try again later.
```
The same command before suspend and again 10 s later both succeed with `verify-match`, exit 0 — so the reader itself is healthy throughout.
The livelock needs one seed failure to enter. Resume from suspend supplies it: the device is genuinely unavailable for a moment as USB re-enumerates (`usb 3-7: reset full-speed USB device number 3 using xhci_hcd` on every resume). A clean lock succeeds on the first attempt and never enters the loop, which is why this presents as random.
## Expected
A transient claim failure should back off and retry, not spin at 4 Hz spawning overlapping subprocesses that guarantee further claim failures.
## Steps to reproduce
1. Laptop with an enrolled fingerprint and `omarchy-lock-fingerprint` in use.
2. Close the lid and reopen it within about a second, so the machine suspends and resumes immediately.
3. Try to unlock by fingerprint.
4. Watch `journalctl -f | grep -E 'omarchy-shell\[|code 9'` — observe multiple concurrent PAM subprocesses per second, all returning code 9.
Not 100% reproducible per attempt, since it depends on catching the device mid-re-enumeration. Roughly 2 storms per day in normal use here.
## Suggested fix
Three things, any of which breaks the loop:
- Give `fingerprintRetryTimer` exponential backoff (e.g. 250 ms → 2 s ceiling) instead of a flat 250 ms.
- Gate `startFingerprint()` on actual subprocess exit rather than on the PAM result, so a still-releasing claim can't be raced.
- Distinguish a *claim failure* (device busy/unavailable — retry with backoff) from a *failed match* (finger rejected — normal retry cadence). Right now both funnel through the same path.
## System details
- Omarchy 4.0.0-1
- Kernel 7.1.8-arch1-3
- Intel Core Ultra X7 358H
- Reader: Goodix MOC `27c6:609c` (Framework Laptop)
- fprintd 1.94.5-2, libfprint 1.94.100-1
- Suspend type: s2idle
Note: USB autosuspend was ruled out as a factor — the reader was pinned to `power/control=on` during testing and the livelock still occurred.
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
- Fixed bypr/11273· confirmed
- Fixed bypr/10653· 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.