Skip to content

Tell iCloud being refused apart from terms of service - #223

Merged
parawanderer merged 3 commits into
mainfrom
fix/icloud-refused-is-not-terms
Sep 16, 2026
Merged

parawanderer merged 3 commits into
mainfrom
fix/icloud-refused-is-not-terms

Conversation

@parawanderer

Copy link
Copy Markdown
Owner

Closes #221.

Apple took the password and the code, then the com.apple.mobileme delegate refused the account: status=1, status-message='A server problem is blocking Apple ID sign in. Try signing in later.', and no localizedError in the response at all.

That is the whole finding. The response has two independent error channels and terms arrive on only one of them. classifyLoginFailure mapped every MobileMeDelegateError to REASON_TERMS, so the reporter — whose terms were fine, as iCloud on the web confirmed — was taken to a terms screen, shown an empty document list, and told that accepting terms was the remedy.

The change

REASON_ICLOUD_REFUSED splits out of REASON_TERMS, branching on whether the response used the localizedError channel at all. The new sentence says authentication worked, says it is not about terms, and passes on what the neighbouring clients have worked out: this turns up on Apple IDs that have never been used with an Apple device, and completing the account at appleid.apple.com is what clears it.

That knowledge is macless-haystack#84, #86 and #87, where the same delegate status arrives both with this wording and with "Account limit reached". They are credited in the source comments, not only here.

Deliberately not REASON_APPLE_DECLINED: that advises waiting and offers the Anisette server route. Apple's own wording here also says to try later, and across those clients it does not clear on its own, so that advice is a loop. A different machine identity changes nothing about an account Apple will not open, which is why the test also pins that the server button stays hidden.

Reads error.localized_error rather than the names_a_localized_error property added in parawanderer/FindMy.py#5, so this works against the pinned commit and does not drag rule 14's four files into the fix. That PR fixes the same confusion in the library's own message and is independent of this one.

Tests

Six bridge tests and one Espresso test. Two of the bridge tests go red against the old classification — verified by reverting the branch and watching them fail, with the old run printing the misleading terms advice.

The string is in all ten locales via add_strings.py; --check passes at 376 strings.

Not verified here: this machine has no Android toolchain, so nothing Java was compiled and the instrumented test has not run. The build and the emulator suite are CI's word, not mine.

🤖 Generated with Claude Code

Issue #221. Apple took the password and the code, then the com.apple.mobileme
delegate refused the account: status=1, status-message "A server problem is
blocking Apple ID sign in. Try signing in later." There was no localizedError
in the response at all.

That matters because the response has two independent error channels and
terms arrive on only one of them. classifyLoginFailure mapped every
MobileMeDelegateError to REASON_TERMS, so the reporter - whose terms were
fine, as iCloud on the web confirmed - was taken to a terms screen, shown an
empty document list, and told that accepting terms was the remedy.

Splits REASON_ICLOUD_REFUSED out of it, branching on whether the response
used the localizedError channel at all. The new sentence says authentication
worked, says it is not about terms, and passes on what the neighbouring
clients have worked out: this turns up on Apple IDs that have never been used
with an Apple device, and completing the account at appleid.apple.com is what
clears it. macless-haystack#84, #86 and #87 are where that comes from, and
they are credited in the comments rather than only here.

Deliberately not REASON_APPLE_DECLINED. That one advises waiting and offers
the Anisette server route; Apple's own wording here also says to try later,
and across those clients it does not clear on its own. Sending somebody to
retry an unchanged sign-in forever is worse than naming the account.

Reads error.localized_error rather than the names_a_localized_error property
added in parawanderer/FindMy.py#5, so this works against the pinned commit
and does not drag rule 14's four files into the fix.

Six bridge tests and one Espresso test. Two of the bridge tests go red
against the old classification, verified by reverting the branch. The Java
is not compiled here - this machine has no Android toolchain - so the
instrumented test and the build are CI's word, not mine.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The same split as the app side, in the other two programs that make this
call. Both the wizard and the CLI answered every MobileMeDelegateError with
the terms flow, because terms were the only cause with a remedy here.

Neither of them lied about it - the wizard fetches, finds nothing and says
"this is something else", and the CLI asks before fetching and says the same.
That honesty is why this never arrived as an exporter bug report. It is still
a round trip to Apple to discover something the response already said, and it
still leaves somebody at a dead end holding Apple's word "server problem",
which reads as "wait" when waiting is exactly what does not work.

not_a_terms_problem() in icloud.py is the shared decision, next to
ExportSourceError and the sign-in that raises it, so the wizard and the CLI
cannot drift apart on it the way three screens in the app once did.

The message says sign-in worked, says it is not terms, quotes Apple verbatim
because that string is the only evidence a report can carry, and passes on
the remedy from dchristl/macless-haystack#84, #86 and #87 - appleid.apple.com
and a payment method - as somebody else's finding rather than as fact. It
contradicts Apple's own "try signing in later" on purpose.

Nine tests, both branches of the decision. 657 passed on the exporter suite,
flake8 clean, and pyright unchanged at its pre-existing count.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Picks up parawanderer/FindMy.py#5, which stops the library's own delegate
error advising the terms flow when the response never used that channel.

All four sites together, per rule 14: the Chaquopy install line, the bridge
tests' requirements, the exporter's pyproject, and uv.lock via `uv lock`.
test_the_whole_repository_pins_one_findmy and
test_pinned_versions_match_the_app_build both pass.

Nothing user-facing depends on this any more - the app and the exporter each
produce their own sentence for this case now, so the library's message is
read by developers rather than shown to anyone. It is still the text that
lands in a log or a stack trace, which is where the next person diagnosing
this will meet it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@parawanderer
parawanderer merged commit 79e7509 into main Sep 16, 2026
9 checks passed
@parawanderer
parawanderer deleted the fix/icloud-refused-is-not-terms branch September 17, 2026 18:54

This branch was successfully deployed

1 active deployment
Android Build — ee0472b4 Deployed Sep 16, 2026 by parawanderer via Instrumented tests (emulator) #278
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.

App: MobileMeDelegateError after 2FA

1 participant