You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Sign-in never completes: 2FA code accepted but the state stays REQUIRE_2FA on a secondaryAuth account #236
Exporter 1.5.1 from source (02fd88e), macOS 26.7, iCloud route.
Sign-in reaches 2FA, I enter the code, Apple takes it — and then the re-auth behind it comes back still asking for 2FA. It loops until it gives up.
INFO Detected 2FA requirement: secondaryAuth
INFO Transitioning login state: LoginState.REQUIRE_2FA -> LoginState.REQUIRE_2FA
INFO Apple took the code and then failed to finish signing in:
Unexpected state after submitting 2FA: LoginState.REQUIRE_2FA
Not #168. There is no 503 anywhere in the log and I'm on the akd fix. I checked the edge block separately and it behaves as you described: Xcode gets 503, akd gets 401.
My account comes back as au = secondaryAuth rather than trustedDeviceSecondaryAuth, so only SMS was offered. Six attempts over an hour, with the built-in anisette and with a local anisette-v3 server, identical every time.
So I patched trusted_device_2fa = True to force the trusted-device flow. Got the code on my Mac, entered it, same result exactly. It isn't the SMS channel.
Two things worth separating:
The sign-in never completes on a secondaryAuth account.
Whatever the cause, it gets classified as the spent-code 503 path, so it waits and sends a fresh code, over and over. I burned a dozen codes before reading the log and realising nothing was going to change.
The exporter treats any UnhandledProtocolError after the submit as the #168 spent-code case (code_was_already_spent, python/exporter/icloud.py:538). So "Apple asked for 2FA again" gets handled as "wait and send a new code". That's why you burned a dozen codes. I'll narrow it separately.
Likely cause of the loop: at the pinned FindMy.py fork, the Grand Slam sign-in identifies as com.apple.akd, but every 2FA call (/auth/verify/phone, …/securitycode, /auth/verify/trusteddevice, GsService2/validate) still sends the Xcode client info, X-Apple-App-Info: com.apple.gs.xcode.auth and User-Agent: Xcode. So the code is verified for one client, and the re-auth that follows comes from another. This is unconfirmed: another user got through 2FA with the same mismatch, so it doesn't break every account.
experiment/236-akd-2fa-headers on the fork makes the 2FA calls identify as akd too. It's one commit, 0768ded, on top of the version the exporter pins. To test it, from python/:
The install replaces your trusted_device_2fa = True edit, so SMS is the only thing that differs. --no-sync matters: plain uv run reinstalls the locked FindMy.py and drops the change.
Try one sign-in. If the log shows Apple took the code and then failed to finish signing in, stop it there with Ctrl-C, or the loop will spend more codes. Either outcome is useful. Please send the log lines from Detected 2FA requirement to the end.
Tried experiment/236-akd-2fa-headers (confirmed installed at 0768ded) with main at 335b258, SMS 2FA, via the wizard. Same result: the code is accepted, the re-auth comes back asking for secondaryAuth again.
Activity
parawanderer commentedon Sep 19, 2026
Two separate faults, both on our side.
The exporter treats any
UnhandledProtocolErrorafter the submit as the #168 spent-code case (code_was_already_spent,python/exporter/icloud.py:538). So "Apple asked for 2FA again" gets handled as "wait and send a new code". That's why you burned a dozen codes. I'll narrow it separately.Likely cause of the loop: at the pinned FindMy.py fork, the Grand Slam sign-in identifies as
com.apple.akd, but every 2FA call (/auth/verify/phone,…/securitycode,/auth/verify/trusteddevice,GsService2/validate) still sends the Xcode client info,X-Apple-App-Info: com.apple.gs.xcode.authandUser-Agent: Xcode. So the code is verified for one client, and the re-auth that follows comes from another. This is unconfirmed: another user got through 2FA with the same mismatch, so it doesn't break every account.experiment/236-akd-2fa-headerson the fork makes the 2FA calls identify as akd too. It's one commit,0768ded, on top of the version the exporter pins. To test it, frompython/:uv pip install "FindMy @ git+https://github.com/parawanderer/FindMy.py@experiment/236-akd-2fa-headers" uv run --no-sync python -m exporter.wizardThe install replaces your
trusted_device_2fa = Trueedit, so SMS is the only thing that differs.--no-syncmatters: plainuv runreinstalls the locked FindMy.py and drops the change.Try one sign-in. If the log shows
Apple took the code and then failed to finish signing in, stop it there with Ctrl-C, or the loop will spend more codes. Either outcome is useful. Please send the log lines fromDetected 2FA requirementto the end.To go back:
uv sync --frozen.davidcoulson commentedon Sep 19, 2026
Tried
experiment/236-akd-2fa-headers(confirmed installed at0768ded) with main at335b258, SMS 2FA, via the wizard. Same result: the code is accepted, the re-auth comes back asking forsecondaryAuthagain.On the good side, #237 worked: it stopped after one code instead of looping.
Only wait out a refusal after a 2FA code, not every protocol error