Stop asking for a bug report when Apple is just declining - #177
Merged
Merged
Conversation
Issue #176, and it is the same 503 as #168 at two call sites nothing covered. That log is worth reading, because half of it is the previous fix working. Apple refused the 2FA submit; the exporter waited, requested a new code, and signed in - GSA authentication successful, LOGGED_IN. Then open_client called request_pet one second later, got another 503, and that one reached the wizard's catch-all: an exception type, a log path, and a link to the issue tracker. The next attempt a minute later was refused at login and did the same. So the user filed an issue, because the program asked them to, about Apple having a bad three minutes. The obstacle was that FindMy.py folded every non-OK status into UnhandledProtocolError, whose meaning is "Apple said something this library does not model" - which is a bug and is worth reporting. Nothing downstream could tell that apart from a refusal without parsing the status back out of the message. So the fork gained AppleServiceUnavailableError, carrying the status and subclassing UnhandledProtocolError so existing handlers keep working, and raises it for 429 and 5xx from both sign-in requests. FindMy.py is re-pinned in all four places per rule 14, verified by installing the new pin and running the bridge suite against it. Here, apple_is_declining walks the cause chain, because the two call sites arrive differently: log_in raises it plainly and open_client raises it several frames down, wrapped. Only the second reached the catch-all, which is why it was the one that produced the issue. The wizard catches the plain case by type and the wrapped case in _report_unexpected; the CLI catches it ahead of its UnhandledProtocolError handler. Nothing retries. The 2FA path can, because it knows what to re-do - the code is spent and a new one can be requested - and that is left alone because #176 shows it working. A refused login or request_pet has nothing to re-do but the whole sign-in, so it says what happened and stops. Twelve tests here, nineteen in the fork. All verified failable: matching by type without walking the chain turns two red, and putting the issue link back in the message turns two more. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
parawanderer
temporarily deployed
to
Android Build
September 6, 2026 09:34 — with
GitHub Actions
Inactive
parawanderer
temporarily deployed
to
Android Build
September 6, 2026 09:34 — with
GitHub Actions
Inactive
The exporter half of this landed with the pin bump; the app reaches the same library and had nothing for the new type. It also had a regression waiting, which is the part worth reading first. **ACodeAppleAlreadyTook.spentIt matched the string "UnhandledProtocolError" in the bridged message.** Its own docstring says there is nothing better to match on "until the fork grows a transient-failure type". The fork now has one, and it is a subclass - so a real 503 arrives as AppleServiceUnavailableError, the match fails, and the 2FA wait-and-retry silently stops firing. Every test in that file writes the old name into its own fixture, so all eight checks stayed green over it. A test that supplies the string it is looking for cannot notice the string changing. It now matches both names, and three tests cover the one that is real. Beyond that, a 503 outside the 2FA path had no classification at all: - classifyLoginFailure sent it to REASON_UNKNOWN, so the login screen echoed "The Grand Slam request was refused with HTTP 503" verbatim. Correct, unreadable, and indistinguishable from a bug here, which is how issue #176 came to be filed. - icloud_bridge._unexpected did the same, so the fetch screen fell to its default branch and put the same text on screen. Both now report apple_declined, which the Java side maps to ICloudFailure.APPLE_DECLINED and PythonAccountLoginException's matching reason. Deliberately not CREDENTIALS_REJECTED: that ends in a forced sign-out, and signing somebody out over a fault that clears in minutes is rule 15's mistake in the direction that costs a working session. Also deliberately not REASON_NETWORK, because the advice differs - a network failure is usually the phone's and worth checking, and this one is not. The login screen gets a sentence saying it is Apple's fault and the password is fine. The fetch screen reaches the retry container it already had, with wording that says which of the two failures it is rather than the raw detail. Fourteen tests. Three JVM for the regression, five JVM for the mapping and the sign-out predicate, six Python across both classifiers, and two Espresso - one per screen - asserting the sentence is shown and that the status code and "Grand Slam" are not. Three strings in ten locales via add_strings.py. Not verified locally: no Android SDK here, so the JVM and emulator suites are CI's to run. The Python bridge suite passes at 239. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
parawanderer
temporarily deployed
to
Android Build
September 6, 2026 10:02 — with
GitHub Actions
Inactive
parawanderer
temporarily deployed
to
Android Build
September 6, 2026 10:02 — with
GitHub Actions
Inactive
Five people have now reported this and only @parawanderer has confirmed it resolving. crishpeen on #168 says the opposite: every attempt, every 2FA method, device-identity.json cleared, still refused - and their log shows login succeeding and only request_pet getting the 503. The message said it "usually clears on its own within a few minutes", which was one confirmed recovery stated as a rule. Somebody it does not clear for would read that and wait. It now says trying again shortly is worth doing, that it has been seen to last, and asks for a report with the log when it keeps refusing. The 2FA message said the same thing and said it immediately after demonstrating that two waits had not cleared it, which was incoherent as well as unsupported. Both Android strings reworded through --show and --replace across ten locales. The test asserting no bug report is mentioned is narrowed to what it was actually protecting: the dialog must not present this as a defect here, which is what linking the issue tracker did. Asking for a report when it persists is correct and now asserted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
parawanderer
added a commit
that referenced
this pull request
Sep 12, 2026
#182 was opened against another branch to stack it on #177, and reported one green check - the Windows binary build, the only workflow with no branch filter. Every workflow that matters is gated `pull_request: branches: [ "main" ]`, so the APK build, the static checks, the Chaquopy bridge tests, the JVM suite and the emulator suite all matched nothing and never ran. It does not present as skipped, it presents as passing, which is the part worth a rule.
This was referenced Sep 13, 2026
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.
Fixes #176.
What the report shows
Half of it is the #168 fix working. Apple refused the 2FA submit, the exporter waited, requested a new code, and signed in:
Then
open_clientcalledrequest_petone second later and got another 503, which reached the wizard's catch-all: an exception type, a log path, and a link to the issue tracker. The next attempt a minute later was refused atloginand did the same. The user filed an issue because the program asked them to, about Apple having a bad three minutes.Three call sites, one outage, one covered.
Why nothing could tell
FindMy.py folded every non-OK status into
UnhandledProtocolError, whose docstring says "This is almost always a bug, so please report it". A 503 is not that: nothing is unmodelled, the server declined. Distinguishing them meant parsing the status back out of the message text.Upstream
parawanderer/FindMy.py@3c2b492 adds
AppleServiceUnavailableError, carrying the status and subclassingUnhandledProtocolErrorso every existingexceptkeeps working.is_service_unavailablecovers 429 and 5xx and deliberately nothing else, since a 401 is permanent until the user acts and waiting that out silently is the same mistake reversed. Raised from_gsa_requestand_sms_2fa_request. 19 tests, suite green at 848.Re-pinned in all four places per rule 14, verified by installing the new pin into a clean venv and running the bridge suite against it: 233 passed.
Here
apple_is_decliningwalks the cause chain, because the two uncovered sites arrive differently:log_inraises it plainly,open_clientraises it several frames down and wrapped. Only the second reached the catch-all, which is why it was the one that generated the issue._report_unexpectedUnhandledProtocolErrorhandlerNothing retries. The 2FA path can, because it knows what to re-do, and #176 shows it working. A refused
loginorrequest_pethas nothing to re-do but the whole sign-in.Tests
12 here, 19 upstream. All verified failable:
590 passed, 2 skippedacrosspython/testandopentagviewer_export. flake8 and pyright clean.Not verified
No Android SDK here, so the app half is CI's to run. The app reaches the same library and will now receive the new type; it has no handler for it yet, and
AppleLoginActivityalready has its own 2FA-specific handling from the earlier fix. Worth a follow-up rather than folding in blind.🤖 Generated with Claude Code