Skip to content

Only wait out a refusal after a 2FA code, not every protocol error - #237

Merged
parawanderer merged 1 commit into
mainfrom
fix/236-only-a-refusal-spends-the-code
Sep 19, 2026
Merged

parawanderer merged 1 commit into
mainfrom
fix/236-only-a-refusal-spends-the-code

Conversation

@parawanderer

Copy link
Copy Markdown
Owner

Refs #236 (fixes the second of the two faults reported there; the first is being tested on parawanderer/FindMy.py experiment/236-akd-2fa-headers).

code_was_already_spent (python/exporter/icloud.py) accepted any UnhandledProtocolError raised by submit, so every such failure went through the #168 recovery: wait, send a new code, submit again, three times. On #236 the failure is Unexpected state after submitting 2FA: LoginState.REQUIRE_2FA, identical on every attempt, so each sign-in spent three codes for nothing.

The same catch took MobileMeDelegateError (an UnhandledProtocolError subclass), so the except MobileMeDelegateError terms handler around log_in in wizard.py and cli.py never ran after a 2FA submit.

Changes:

  • code_was_already_spent now returns true only for AppleServiceUnavailableError, which is what a refused Grand Slam call raises at the pinned FindMy.py.
  • _submit_code_with_retries re-raises anything else unchanged after one submit, with no wait and no new code.
  • test_retries.py: the spent-code fake now raises AppleServiceUnavailableError; new TestAFailureThatIsNotWeather covers the REQUIRE_2FA error, a non-refusal status, and MobileMeDelegateError, asserting one submit, zero code requests, and the original exception.

Verified: uv run pytest ./test ./opentagviewer_export (680 passed, 2 skipped) and flake8. With the old one-line classifier restored, all four new tests fail. Not run against Apple.

Drafted by Claude Code

🤖 Generated with Claude Code

code_was_already_spent accepted any UnhandledProtocolError raised after
the submit, so every such failure got the #168 recovery: wait, send a new
code, try again. That recovery is only right for Apple refusing the
request. #236 is Apple taking the code and asking for verification again,
identically each time, and the loop spent three codes per sign-in on it.

It also swallowed MobileMeDelegateError, an UnhandledProtocolError
subclass, so the terms handler both front ends put around log_in could
not run after 2FA.

Now only AppleServiceUnavailableError counts as a spent code; anything
else propagates unchanged after one submit.

Refs #236

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@parawanderer
parawanderer merged commit 335b258 into main Sep 19, 2026
6 checks passed
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.

1 participant