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-1754GitHub issuePublicFixed

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.

Received and recorded verbatim. Nothing has been checked yet.

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

Lock screen fingerprint retry livelocks after resume: overlapping PAM subprocesses collide on exclusive fprintd claimrevision 1 · captured 2026-09-28 21:29 UTC via poll

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:

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

  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.

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.