Skip to content

fix: don't evict LAContext on user-cancelled Touch ID prompts#155

Merged
jgowdy-godaddy merged 1 commit into
mainfrom
fix-lacontext-eviction-on-user-cancel
May 21, 2026
Merged

fix: don't evict LAContext on user-cancelled Touch ID prompts#155
jgowdy-godaddy merged 1 commit into
mainfrom
fix-lacontext-eviction-on-user-cancel

Conversation

@jgowdy-godaddy
Copy link
Copy Markdown
Contributor

Problem

Users experiencing repeated Touch ID prompts and eventual password fallback after cancelling Touch ID 3+ times. The git pull error showed:

sign_and_send_pubkey: signing failed for ECDSA "..." from agent: agent refused operation

Agent logs showed:

  • SE_ERR_SIGN (code 3): signing failed
  • SE_ERR_KEYCHAIN_LOAD (code 10): keychain load failed

Both errors triggered LAContext eviction, forcing fresh Touch ID prompts on every retry even though the LAContext was still valid.

Root Cause

Generic Swift error codes (3, 10) collapsed all failures into a single code, losing the actual OS error details (LAError.userCancel, errSecUserCanceled). Rust couldn't distinguish "user cancelled" from "real error" and evicted the LAContext on both.

Failure cycle:

  1. Sign fails (user cancels Touch ID)
  2. LAContext evicted
  3. Next sign creates fresh LAContext → fresh Touch ID prompt
  4. User cancels again → evicted again
  5. After 3+ cancellations, macOS falls back to password

Solution

  • Add SE_ERR_USER_CANCEL (code 16) for explicit user cancellation
  • Map LAError.userCancel and errSecUserCanceled to the new code
  • Add Error::UserCancelled variant to Rust
  • Update should_evict_lacontext to NOT evict on UserCancelled
  • Add error logging to Swift bridge catch blocks for diagnostics

Result

When a user cancels Touch ID:

  • LAContext stays cached
  • Next sign within cache TTL reuses same LAContext (no prompt spam)
  • Agent logs show actual error: "enclaveapp: se_sign: user cancelled (LAError -2)"

Testing

  • All existing tests pass
  • Added test for Error::UserCancelled display message

Prevents LAContext cache thrashing when users cancel Touch ID prompts,
which was causing repeated Touch ID prompts and eventual password
fallback after 3+ cancellations.

Changes:
- Add SE_ERR_USER_CANCEL (code 16) to Swift bridge for explicit user
  cancellation (LAError.userCancel, errSecUserCanceled)
- Add Error::UserCancelled variant to Rust error enum
- Update should_evict_lacontext to NOT evict on UserCancelled
- Add error logging to Swift bridge catch blocks to show actual
  NSError/LAError details in agent logs

Root cause: Generic error codes (SE_ERR_SIGN=3, SE_ERR_KEYCHAIN_LOAD=10)
were collapsing all failures into a single code, causing Rust to evict
the LAContext on user cancellation. The LAContext is still valid when
the user cancels — evicting it forces a fresh Touch ID prompt on the
next operation with no gain.

With this fix, cancelling a Touch ID prompt preserves the LAContext
cache, so subsequent operations within the cache TTL window succeed
silently without re-prompting.
@jgowdy-godaddy jgowdy-godaddy merged commit c22d72c into main May 21, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants