Skip to content

Retry the refusals that clear on their own, and pick up the timeout fix - #230

Merged
parawanderer merged 1 commit into
mainfrom
fix/retry-apple-refusals-and-bump-the-pin
Sep 16, 2026
Merged

parawanderer merged 1 commit into
mainfrom
fix/retry-apple-refusals-and-bump-the-pin

Conversation

@parawanderer

Copy link
Copy Markdown
Owner

Both halves come from failures met while testing the 1.1.0 candidate today.

The pin moves to 7969003

Carries parawanderer/FindMy.py#6. The escrow proxy and the CloudKit client each built their own HttpSession with the library's five-second default, whatever the account had been told — so a phone signed in happily on a thirty-second timeout and then failed with:

TimeoutError: POST .../escrowproxy/api/srp_init did not answer within 5s

…with nothing in this app able to reach the value the message told it to change. All four pin sites move together per rule 14. The exporter picks up the same fix, which matters as much: five seconds on an escrow SRP exchange will fail for anyone on mobile data.

Apple's occasional refusal is retried rather than shown

Closes #226. The edge refuses the odd request on a fresh connection and answers the next one — measured with a probe on that issue, and met twice in one sitting on the account-setup screen, cleared by pressing Try Again both times. The app was asking somebody to do by hand what it can do in a second.

Two retries, 1s then 3s. Apple's own Retry-After wins when it sends one; a refusal naming a wait longer than five seconds is surfaced rather than slept through, because a screen that silently blocks for a minute is worse than one that says how long. Four seconds of total patience cannot hide a real outage — the September 2026 edge block lasted days and would still reach the screen.

Three calls are deliberately not wrapped

This is the part worth reviewing:

Call Why not
session.join Writes. Enrols an escrow record, and its own docstring says a timeout does not establish that nothing was sent — so a retry can enrol twice, and duplicate records are exactly what #224 had to filter out of the recovery picker
session.recover Spends one of a capped and unrecoverable number of passcode attempts. exporter.icloud.unlock is explicit that the decision to spend another is always the user's — still true when the caller is a loop
client.rename Writes

Retried: opening the client, listing what can be recovered from, resuming, fetching. Reads only.

Tests

Ten. Three assert the three calls above are not retried, which is the half worth having. Three more fail without the wiring, verified by removing it; the rest cover the backoff, Retry-After winning over it, and a long wait being surfaced rather than slept.

312 bridge tests and 676 exporter tests pass against the new pin, flake8 clean, and both rule 14 pin assertions hold.

Not verified here: no Android toolchain on this machine, so nothing Java was compiled — though this change is Python and Gradle only.

🤖 Generated with Claude Code

Two things, both from failures met while testing the 1.1.0 candidate.

**The pin moves to 7969003**, which carries parawanderer/FindMy.py#6: the
escrow proxy and the CloudKit client built their own HTTP sessions with the
library's five second default, whatever the account had been told. So a
phone signed in happily on a thirty second timeout and then failed with
`srp_init did not answer within 5s`, and nothing in this app could reach the
value to change it. All four pin sites move together, per rule 14. The
exporter picks the same fix up, which matters as much: five seconds on an
escrow SRP exchange will fail for anybody on mobile data.

**And Apple's occasional refusal is retried rather than shown.** The edge
refuses the odd request on a fresh connection and answers the next one -
measured in #226, and met twice in one sitting on the account-setup screen,
cleared by pressing "Try Again" both times. The app was asking somebody to
do by hand what it can do in a second. Two retries, 1s then 3s, with
Apple's own Retry-After winning when it sends one, and a refusal naming a
wait longer than five seconds surfaced rather than slept through.

**Three calls are deliberately not wrapped, and that is the careful part.**
`join` writes - it enrols an escrow record, and its own docstring says a
timeout does not establish that nothing was sent, so a retry can enrol
twice and duplicate records are exactly what the recovery picker now has to
filter out. `session.recover` spends one of a capped and unrecoverable
number of passcode attempts, and exporter.icloud.unlock is explicit that
the decision to spend another is always the user's - still true when the
caller is a loop. `rename` writes too. Reads only: opening the client,
listing what can be recovered from, resuming, and fetching.

Ten tests. Three of them assert the three calls above are *not* retried,
which is the half worth having; the other three fail without the wiring,
verified by removing it. 312 bridge tests and 676 exporter tests pass
against the new pin, flake8 clean, and both rule 14 pin assertions hold.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@parawanderer
parawanderer merged commit bf9cf9b into main Sep 16, 2026
9 checks passed
@parawanderer
parawanderer deleted the fix/retry-apple-refusals-and-bump-the-pin branch September 17, 2026 18:55

This branch was successfully deployed

1 active deployment
Android Build — 16eb5bd9 Deployed Sep 16, 2026 by parawanderer via build #293
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.

Grand Slam still returns a sporadic 429, on a fresh connection

1 participant