Retry the refusals that clear on their own, and pick up the timeout fix - #230
Merged
Merged
Conversation
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>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Both halves come from failures met while testing the 1.1.0 candidate today.
The pin moves to
7969003Carries parawanderer/FindMy.py#6. The escrow proxy and the CloudKit client each built their own
HttpSessionwith 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:…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-Afterwins 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:
session.joinsession.recoverexporter.icloud.unlockis explicit that the decision to spend another is always the user's — still true when the caller is a loopclient.renameRetried: 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-Afterwinning 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