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-443GitHub issuePublicValidated· Reproduced

Lock screen fingerprint sensor only ever runs ONE pam session per lock

Reported by slr01 · filed Sep 16, 2026 · open at source

Validation verdict: Reproduced. An engine-executed run exited 1 and showed the defect signal. Awaiting a human decision.

At a glance

1 · What is claimed?
  • ConfirmedReproducible: the fingerprint scan keeps being re-armed while the displays are blank (250 ms after a miss)
  • Not confirmedAffects the lock component
3 · Is it reproducible?
An engine-executed run exited 1 and showed the defect signal. See reproducer
4 · What remains unclear?
  • Open question for maintainers: Open decision: when should the lock screen scan for a fingerprint, and should retries end? Options: (A) keep the current behaviour (scan for the whole lock, also while the displays are blank; 250 ms after a miss, error backoff to 40 s, no total budget); (B) start no scan while the displays are blank and re-arm on wake (PRs #12420, #7179); (C) a total attempt or time budget per lock, value chosen by the maintainers (#10078's ask, PR #10082).
  • Open question for maintainers: The same decision is recorded for omacom/omarchy#9913 (R-1029): both report branches carry the same draft requirement (one id, identical file), so approving it from either branch settles both reports.
  • Open question for maintainers: The decision is the Omarchy maintainers'; a draft requirement listing these options sits on this report's branch (report/cmulr8r7a0iwl1gw4uqg4huff), and approving one option = merging that draft into the baseline with the chosen text.
  • Affects the lock component — no requirement of the lock component is confirmed as violated
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. So far: in the audited area (lock) — decided by agents · verified; matches no known issue — decided by agents · verified. The outcome (valid, duplicate, not a bug, …) is not decided yet. See the decision.

Open question — draft spec awaiting a decision

Draft — awaiting a decisionSW-REQ-261005-CXRPcomponent lockrefines When the user locks the session, the system shall engage… SYS-REQ-260912-T0XP

When shall the lock screen scan for a fingerprint, and shall retries end?

What the code does today

(upstream quattro 035ce29f, after upstream #7158) after an attempt that reached the reader and did not match, the next attempt is armed in 250 ms (FingerprintModel.js:3, Service.qml:420); attempts that do not reach the reader or end in a fast error back off 1 s doubling to a 40 s cap (FingerprintModel.js:7-23); runBlank() leaves a pending retry running (Service.qml:294-298) and startFingerprint() does not check displaysBlank (Service.qml:357-375), so scanning goes on while the displays are blank, for as long as the session is locked.

Options (listed neutrally — the choice is the maintainers')
  1. AKeep the current behaviour
  2. BThe lock service shall start no fingerprint scan while the displays are blank and shall re-arm the reader on wake
  3. CA total attempt or time budget per lock, value chosen by the maintainers

Approving this draft means choosing one option; its text then replaces this description.

Why this is an open question

Raised by omacom/omarchy#9913 (proof inbox R-1029) and omacom/omarchy#12202 (proof inbox R-443); the retry-budget half of omacom/omarchy#10078 asks the same. Validations: report/cmulr91vo0k9f1gw499ic4jis (rval-745154880ad90c47c99d5d68a7d1114c) and report/cmulr8r7a0iwl1gw4uqg4huff (rval-4da3f7f4c6cb4bb2c50649cba8ac53f4). #12202's premise (no new PAM session after a miss) does not hold at this commit; #9913's flat 250 ms retry on errors was replaced by #7158's backoff. Open upstream PRs #12420 and #7179 implement option B; PR #10082 proposes option C.

Proposed on this report's branch @ de27fafd37 · specs/software/requirements/SW-REQ-261005-CXRP.req.yaml · not part of the audited spec until approved and merged

Written after the validation was sealed at b2966f67a9: the seal covers the reproducer and the observed behaviour, not this proposed text.

Original report

Lock screen fingerprint sensor only ever runs ONE pam session per lockrevision 1 · captured 2026-09-28 21:23 UTC via poll

ISSUE — Lock screen fingerprint sensor only ever runs ONE pam session per lock

Summary

The Omarchy lock screen (shell/plugins/lock/Service.qml) creates a single Quickshell PamContext for fingerprint auth and never starts a second one for the duration of a lock. After the first scan returns no-match or the driver errors, fingerprintPam.active stays truthy, so startFingerprint() bails out of every later attempt. Result: fingerprint unlock works at most once per lock, and if your first press misses (finger slipped, touched early, reader was still warming up), the reader is dead until you unlock by password and lock again.

Reproduction

Omarchy 4.0.4, laptop with an integrated fingerprint reader (tested: Broadcom ControlVault 3, USB 0a5c:5843, via libfprint-2-tod1-broadcom / fprintd 1.94.5).

  1. Enroll a finger, lock.
  2. Press the reader and get a miss/no-match (or wait out the 30s window without touching).
  3. Press again — nothing happens, until you unlock with the password.

Details that make it visibly worse on this hardware:

  • The reader needs the driver warmed up to take a pass, so the first press in a new lock often errors (Identification failed due to unforeseen error after ~10s). With the current design that one error is fatal for the whole lock.
  • The lock keeps the fingerprint PAM armed (scanning) continuously while the display is blanked, which drives the Broadcom 58200 into its thermal cutoff (Device disabled to prevent overheating) after a few minutes of being locked.

Evidence

fprintd debug logs across one lock, stock Omarchy:

15:01:45 identify()            <- first (and only) session
15:01:55 Identification failed due to unforeseen error!
... user holds a finger for 60s after wake ...
15:02-15:06 (nothing)           <- reader never re-arms

And with the fix below:

15:11:17 session #1: identify() -> errors after 10s
15:11:27 session #2+ re-arm on the 250ms retry
15:14:10 fresh session on wake
15:14:18 verify-match            <- unlock works after a miss, after walking away

Root cause

Service.qml uses a single PamContext { id: fingerprintPam } whose active never returns to false after a completed/errored session within one lock. The retry Timer fires every 250ms but startFingerprint() guards on fingerprintPam.active || fingerprintAuthenticating and returns forever.

Proposed fix

See PR (re-arm manages a fresh PamContext per attempt):

  • Replace the single PamContext with a Component factory creating a new instance per attempt.
  • While the display is blanked (runBlank), stop the retry timer and gate startFingerprint() on the existing displaysBlank state so no NEW scan starts while the sensor is idle and could overheat; re-arm on wake (runWake). An in-flight scan is left to finish — the reader is not a compositor wake source, and aborting the pam session in runBlank severed a scan that the driver was still servicing, leaving the reader dead until password unlock.

Notes

  • This is not reader-specific: any fingerprint reader that errors or misses on its first scan of a lock is affected. The thermal behavior just makes the first-scan error common on some TOD/LS drivers.
View raw text
# ISSUE — Lock screen fingerprint sensor only ever runs ONE pam session per lock

## Summary

The Omarchy lock screen (`shell/plugins/lock/Service.qml`) creates a single Quickshell `PamContext` for fingerprint auth and never starts a second one for the duration of a lock. After the first scan returns no-match or the driver errors, `fingerprintPam.active` stays truthy, so `startFingerprint()` bails out of every later attempt. Result: **fingerprint unlock works at most once per lock**, and if your first press misses (finger slipped, touched early, reader was still warming up), the reader is dead until you unlock by password and lock again.

## Reproduction

Omarchy 4.0.4, laptop with an integrated fingerprint reader (tested: Broadcom ControlVault 3, USB `0a5c:5843`, via `libfprint-2-tod1-broadcom` / fprintd 1.94.5).

1. Enroll a finger, lock.
2. Press the reader and get a miss/no-match (or wait out the 30s window without touching).
3. Press again — nothing happens, until you unlock with the password.

Details that make it visibly worse on this hardware:

- The reader needs the driver warmed up to take a pass, so the first press in a new lock often errors (`Identification failed due to unforeseen error` after ~10s). With the current design that one error is fatal for the whole lock.
- The lock keeps the fingerprint PAM armed (scanning) continuously while the display is blanked, which drives the Broadcom 58200 into its thermal cutoff (`Device disabled to prevent overheating`) after a few minutes of being locked.

## Evidence

fprintd debug logs across one lock, stock Omarchy:

```
15:01:45 identify()            <- first (and only) session
15:01:55 Identification failed due to unforeseen error!
... user holds a finger for 60s after wake ...
15:02-15:06 (nothing)           <- reader never re-arms
```

And with the fix below:

```
15:11:17 session #1: identify() -> errors after 10s
15:11:27 session #2+ re-arm on the 250ms retry
15:14:10 fresh session on wake
15:14:18 verify-match            <- unlock works after a miss, after walking away
```

## Root cause

`Service.qml` uses a single `PamContext { id: fingerprintPam }` whose `active` never returns to `false` after a completed/errored session within one lock. The retry `Timer` fires every 250ms but `startFingerprint()` guards on `fingerprintPam.active || fingerprintAuthenticating` and returns forever.

## Proposed fix

See PR (re-arm manages a fresh `PamContext` per attempt):

- Replace the single `PamContext` with a `Component` factory creating a new instance per attempt.
- While the display is blanked (`runBlank`), stop the retry timer and gate `startFingerprint()` on the existing `displaysBlank` state so no NEW scan starts while the sensor is idle and could overheat; re-arm on wake (`runWake`). An in-flight scan is left to finish — the reader is not a compositor wake source, and aborting the pam session in `runBlank` severed a scan that the driver was still servicing, leaving the reader dead until password unlock.

## Notes

- This is not reader-specific: any fingerprint reader that errors or misses on its first scan of a lock is affected. The thermal behavior just makes the first-scan error common on some TOD/LS drivers.

Also reported as

This defect is tracked under R-443 (filed Sep 16, 2026). 2 other reports describe the same defect.

Earliest report of this defect: R-1029 (filed Sep 3, 2026).

  1. Sep 16, 2026R-443 · this report— tracked here
  2. ·Decided by agents · verified

    Open upstream.

  3. ·Decided by agents · verified

    Open upstream.

Validation

AI validator claude-code (claude-opus-5.5) · evaluated revision 1 at product commit 1e43373287af · sealed 2026-10-05T14:30:14Z

Validator's summary (commentary, not evidence)

Validation of omacom/omarchy#12202 on quattro-proof 1e433732 (upstream quattro 035ce29f product code plus the proof layer). The report's headline premise does not hold at this commit: after an attempt that reached the reader and did not match, the lock arms a new attempt in 250 ms, so fingerprint unlock is not limited to one PAM session per lock (the reporter's own stock A/B capture in the thread reached the same conclusion; the triage bot's 2026-10-04 comment says #7158 addressed the rapid error retries). Its second point holds: the reader keeps being scanned while the display is blanked. At this commit (upstream #7158 merged, which is in upstream quattro 035ce29f), the real Service.qml retry policy is: after an attempt that reached the reader and did not match, the next attempt is armed in 250 ms (FingerprintModel.js:3 and Service.qml:420 upstream); attempts that never reach the reader or end in a fast error back off 1, 2, 4 ... 32 s and then 40 s (FingerprintModel.js:7-23); runBlank() leaves a pending retry running (Service.qml:294-298) and a miss while the displays are blank arms the next attempt in 250 ms (startFingerprint, Service.qml:357-375, has no displaysBlank check); retries continue for as long as the session stays locked. SW-REQ-261004-296X covers the unavailable report and SW-REQ-260912-ND55 says the armed fingerprint PAM must not hold the display lit; no requirement says whether the reader is scanned while the displays are blank or whether retries have a total budget. That is an open specification question for the maintainers. Open upstream PRs #12420 and #7179 propose not scanning while blank. a withheld known issue (fixed) was examined: the start-failure path, not this one.

  • ConfirmedReproducible

    Reproducible: the fingerprint scan keeps being re-armed while the displays are blank (250 ms after a miss)

    Engine: engine-executed run exited 1 and showed the defect signal

    Evidence: file test/reports/report-cmulr8r7a0iwl1gw4uqg4huff.sh

    Validator's rationale (commentary): Observed with the real Service.qml: runBlank() leaves the pending retry running and a miss with displaysBlank=true arms the next attempt in 250 ms. The headline claim (no new PAM session after a miss) is not reproduced: a miss arms a new attempt in 250 ms.

  • Not confirmedAffects a componentlock

    Affects the lock component

    Engine: no requirement of the lock component is confirmed as violated

Engine reference checks at b2966f67a904 (12/12 ok)
  • okcommitb2966f67a904542323b723e7cad39421c7cedfaa
  • okbase commit1e43373287afbd6dca4674ad463d5b5db7812358
  • okbase ancestry1e43373287afbd6dca4674ad463d5b5db7812358
  • okproduct code untouchedthis report's reproducer branch
  • okbranchthis report's reproducer branch
  • okfiletest/reports/report-cmulr8r7a0iwl1gw4uqg4huff.qml
  • okfiletest/reports/report-cmulr8r7a0iwl1gw4uqg4huff.sh
  • okrequirementSW-REQ-260912-41VV
  • okrequirementSW-REQ-260912-ND55
  • okrequirementSW-REQ-261004-296X
  • okcomponentlock
  • okknown issuewithheld

The engine's own check, at validation time, that the commits, the reproducer branch (touching only reproducer files), the reproducer files and every mapped requirement / component / known issue really exist.

Validator-authored hypothesis. Each claim's verification is engine-computed: confirmed only from an engine-executed reproducer run recorded in this record, refuted when recorded evidence contradicts it. Prose is commentary. The disposition of the report is a human decision.

Reproducer

engine-runexit 1 · expected exit 0 · defect exit 1 · 664 ms · 2026-10-05T14:30:13Z→ showed the defect
test/reports/report-cmulr8r7a0iwl1gw4uqg4huff.shon this report's reproducer branch @ b2966f67a9+ test/reports/report-cmulr8r7a0iwl1gw4uqg4huff.qml
bash test/reports/report-cmulr8r7a0iwl1gw4uqg4huff.sh

Setup failure: exit 2 means the reproducer could not set up (e.g. the product did not build) — such a run decides nothing.

Defect signal: output contains AS-REPORTED:

Matched lines (engine, over the full output)
AS-REPORTED: the reader keeps being re-armed while the displays are blank (the report's second point); its headline premise does not hold at this commit: a miss arms a new attempt in 250 ms
Observed output · 751 bytes
Observed on this tree (quickshell 0.3.1, lock plugin shell/plugins/lock):
  - after one attempt that reached the reader and did not match: the next attempt is armed in 250 ms
  - runBlank() leaves the pending retry running; with displaysBlank=true a miss arms the next attempt in 250 ms
  - 9 unreached/error attempts in a row arm the next attempt after [1000,2000,4000,8000,16000,32000,40000,40000,40000] ms (unavailable notice: true)
  - after them the retry is still armed and the lock is still requested (true): retries go on while the session stays locked
AS-REPORTED: the reader keeps being re-armed while the displays are blank (the report's second point); its headline premise does not hold at this commit: a miss arms a new attempt in 250 ms

Needs: linux · quickshell (0.3.x) and jq on PATH; a sway binary for a private headless compositor (PROOF_ENV_DIR/sysroot/usr/bin/sway, else sway on PATH); the script exits 2 with SETUP: otherwise. Nothing to build: the QML is loaded straight from the worktree · ran on linux/amd64

Linked

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.

  • Decided by agents · verified: in the audited area (lock)
  • Decided by agents · verified: matches no known issue

The outcome (valid, duplicate, not a bug, …) is not decided yet.