Repository navigation
The enrollment screen keeps trying after the device is enrolled, and force-quit is the only way out #105
Description
Migrated from the legacy private tracker (legacy no. 59, opened 2026-08-20). References written as legacy no. N point at that archive and do not resolve in this repo.
Why
The enrollment screen keeps trying after the device is already enrolled, and the
only way out is force-quitting the app.
Measured on the second Pixel, 2026-08-20, enrolling for the first time:
22:55:15 GET .../identity 200 certificate handed over, stored
22:57:52 POST /v1/enroll/requests 409 "presentation refused" code-used
The device held a valid identity at 22:55:15 and the screen was still presenting
at 22:57:52. It would have gone on doing that indefinitely. The operator's
words: "i had to force quit, thats not a good ux".
Two defects compose into it:
1. EnrollActivity never asks whether the device is already enrolled. It
builds a FileIdentityStore and never calls hasIdentity(). This is the same
defect legacy no. 26 fixed for the home screen - the fix moved the LAUNCHER to a status
screen, and left the enrollment screen itself able to enroll a device that has
nothing to enroll.
2. The retry loop re-presents a spent code forever. EnrollActivity:92-100
re-presents lastCode on a postDelayed, and the comment above it is right
about why an EMPTY code was worse. But nothing in that loop re-checks whether
the device became enrolled while it was waiting, and code-used is not a
condition that improves by being retried. A pairing code is single-use by
design; retrying one is asking a question that has a permanent answer.
Why it looks like a failure when it is a success. From the outside this is
indistinguishable from "enrollment did not work" - the operator sees a screen
asking them to join, and a server log full of refusals. It cost real confusion
tonight: the enrollment had in fact succeeded two minutes earlier.
It is also specific to the hands-free path. On the typed path a person is
watching the screen when the certificate arrives. On a scanned enrollment the
activity that presented is torn down when setup wizard hands over to the home
screen, so the person opens the app later, gets the enrollment form, and starts
a second presentation - which is how the spent code gets into the retry loop at
all.
What
EnrollActivityrefuses to present whenhasIdentity()is true, and says
what the device already is rather than offering a form.- A refusal that cannot improve by being retried is not retried.
code-usedis
terminal for that code; so is a fingerprint mismatch.no-such-codeand a
network failure are not. - The retry loop re-checks identity before each attempt, so a certificate that
arrives by any route ends the loop. - Whatever the screen does, it must be escapable without force-quitting.
Acceptance criteria
- An enrolled device opening the enrollment screen is told it is enrolled
- A spent code is never re-presented
- A certificate arriving while the screen is open ends the retry loop
- A terminal refusal is distinguishable on screen from a transient one
- Verified on a handset by enrolling, then reopening the enrollment screen
Out of scope
- Resuming a poll across an activity restart, which needs the request id
persisted. Filed separately - it is what makes the hands-free path need a
second presentation at all.
Estimate
Size: S
Activity
quadseven commentedon Oct 10, 2026
Boxes 1 to 4 met by #119 and its tests (verified: removing the two guards fails both new tests). Box 5 needs a handset, so this issue stays open and the PR references it instead of closing it.
Exact ask: on an enrolled Pixel, open the Muster enrollment screen and confirm it reads 'This device is already enrolled.' with the code field and button disabled and back still leaves. Then reply here, or tick box 5 and close.
fix(agent): enrollment screen refuses when already enrolled; retry lo…