Sign out normally, then create a second account, and the app refused it
with "Your session expired. Sign out and back in to keep going." That is
advice nobody can act on: you have already signed out, and the screen
saying it is the one you sign up from.
The cause is one line doing the right thing in the wrong case.
`authenticate` reuses the device identity stored in the vault, and the
comment above it says exactly why: keeping the device is what stops
signing out and back in costing every contact a fresh trust prompt.
Plain sign-out keeps the device on purpose; only the erase door destroys
it.
That reasoning holds for the same account and breaks for a different
one. The second sign-up reused a device that already belonged to the
first account and tried to register it against the new one. The relay
refused, correctly, and the refusal reached the person as a sentence
about a session they did not have.
The stored credential already carries `userId` and `relayBaseUrl`, so
the check is cheap: same relay and same user means the device is ours to
reuse, anything else is somebody else's account on a shared phone and
gets its own device. Which is what "a device belongs to an account"
already meant everywhere else.
Reproduced and fixed on hardware, not argued: `SignUpFlowTest` against
relay1 failed this way on every run before, and both of its tests pass
after. That suite is the regression guard, and !122 is what made it able
to run at all.
JVM suites unchanged: 1294 tests, no failures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Five things were wrong, each hidden behind the one in front of it, and
none of them was the app. Sign-up works: the run that finally got
through created `andt34972750` on relay1 and registered a device, and
the Account screen showed both.
1. The emulator still had a RELEASE-signed build on it, so the debug
test APK could not install: INSTALL_FAILED_UPDATE_INCOMPATIBLE. Zero
tests ran. The uninstall lived only in the "emulator already up"
branch, on the reasoning that a cold boot starts clean. A cold boot
starts with no snapshot; the disk image keeps what was installed.
The script already PRINTED the leftover package and carried on, which
is a check nobody acts on. It now clears on both paths and refuses to
continue if anything is left.
2. The sign-in form scrolls, and `toggle-register` is below the fold.
A tap aimed off-screen is not an error in Compose, it simply does not
happen, so the form stayed on sign-in and the test waited thirty
seconds for a register-only field. Same for `sign-out`, which is the
last section of a long Account screen.
3. It waited for `device-id` after sign-up. Sign-up lands on Home and
`device-id` is on Account, so it waited two minutes on a screen that
was never going to show it and reported that the app had not signed
in. It had: the dump said "Nobody here yet. Add someone by the
username they signed up with", which is the empty contact list of an
account created seconds earlier. It now waits for the nav pill and
checks the device id on the screen that has it.
4. `performTextInput` appends, and only the relay field was cleared, so
the second test in the class typed "19901990" into the age gate.
Submit is enabled on exactly four digits, a disabled button swallows
a tap, and the wait then blamed a screen the tap never reached. The
same shape as the age-gate bug this suite was fixed for once already.
`signingOutReturnsToSignIn` now passes, against a real relay, which it
has not done in a long time. The remaining failure is not a test
problem and is filed separately: creating a second account after signing
out is refused with "Your session expired", because `signOut` clears the
session in memory and leaves the stored credential on disk.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The module build directories are rewritten every job on a machine that
is also the mac-local runner for both app repos, so indexing them lands
in the merge path for everything. `mds_stores` and `mdworker_shared` sit
at the top of CPU while no build process is near it, and the load
average goes into the hundreds.
Measured on the iPhone repo, same from-scratch build twice: it peaks at
load 12.5 with its output excluded and over a hundred without, and the
indexing work stops accumulating rather than trailing the build by
minutes.
The markers are written by the job rather than committed, because the
directories they belong in are created by the build and removed between
runs. The stable caches outside the repo were excluded on the host
itself: DerivedData, the Android SDK, the runner's builds directory and
the Gradle home, 11 GB between them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>