Skip to content

Use a separate connection for each GrandSlam request - #52

Open
sshane wants to merge 2 commits into
rileytestut:notarizedfrom
sshane:grandslam-connection-fix
Open

Use a separate connection for each GrandSlam request#52
sshane wants to merge 2 commits into
rileytestut:notarizedfrom
sshane:grandslam-connection-fix

Conversation

@sshane

@sshane sshane commented Sep 6, 2026

Copy link
Copy Markdown

Signing in has been failing since roughly 2026-08-31 with NSCocoaErrorDomain 3840, "The data couldn't be read because it isn't in the correct format."

Cause

Apple's edge in front of gsa.apple.com now serves at most two requests per connection and answers everything after that with a 503 HTML error page.

Authenticating takes three requests, and URLSession sends them over one pooled connection:

o=init       200
o=complete   200   ec=0  hsc=200      <- password accepted
o=apptokens  503   <html>503 Service Temporarily Unavailable ... Apple</html>

The password is accepted; only the token exchange fails. The 503's HTML body is then parsed as a property list, which is where the 3840 error comes from — the message describes the parser, not the failure.

The 503 is generated at the edge rather than by GrandSlam: successful responses carry Content-Type: text/x-xml-plist and X-Apple-I-Retry-Request-UUID, while the 503 has Content-Type: text/html and neither.

Reproducing

Any POST body works, valid or not — only the position in the connection matters:

# 3rd request on a reused connection 503s; the first two do not
curl --http1.1 -w 'http=%{http_code}\n' -o /dev/null \
  -X POST -H 'Content-Type: text/x-xml-plist' --data-binary @body.plist \
  https://gsa.apple.com/grandslam/GsService2 \
  --next -w 'http=%{http_code}\n' -o /dev/null \
  -X POST -H 'Content-Type: text/x-xml-plist' --data-binary @body.plist \
  https://gsa.apple.com/grandslam/GsService2 \
  --next -w 'http=%{http_code}\n' -o /dev/null \
  -X POST -H 'Content-Type: text/x-xml-plist' --data-binary @body.plist \
  https://gsa.apple.com/grandslam/GsService2

# http=200
# http=200
# http=503

The same request on a fresh connection returns 200. Retrying does not help, because the retry reuses the same pooled connection.

Changes

  1. Use a separate connection for each GrandSlam request. Each request gets its own ephemeral session, so apptokens arrives first on a fresh connection.
  2. Report 5xx responses instead of failing to parse them. The status code is currently never checked, so any server error surfaces as a parse failure. This is independent of the first commit and useful on its own.

Verified end to end against a real Apple ID: initcompleteapptokens all return 200, and sign-in and refresh work again on both AltServer and on-device AltStore.

This looks like the cause behind the recent reports in altstoreio/AltStore#1776, #1777, #1781, #1782 and SideStore/SideStore#1459, #1464, #1465, which are all the same 3840 error.

Targeting notarized since that is what AltStore Classic pins; marketplace compiles the SRP paths out.

sshane and others added 2 commits September 5, 2026 17:03
Apple's edge began serving at most two requests per connection around
2026-08-31, answering everything after that with a 503 HTML error page.
Authenticating takes three requests -- init, complete, and apptokens --
and URLSession sends them over one pooled connection, so apptokens was
rejected every time and signing in always failed at the last step.

Give each request its own session, and therefore its own connection.

Verified by replaying a captured request: identical bytes succeed on a
fresh connection and return 503 as the third request on a reused one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WrnGBkYZDRb57McNgLxCaE
The response body is parsed as a property list before the status code is
checked, so an HTML error page from Apple surfaces as NSCocoaErrorDomain
3840, "The data couldn't be read because it isn't in the correct format."
That describes the parser rather than the failure, and sends users looking
at their Apple ID, their anisette server, and their OS version.

Check for a 5xx first and report it as what it is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WrnGBkYZDRb57McNgLxCaE
@sshane

sshane commented Sep 6, 2026

Copy link
Copy Markdown
Author

Measured the per-connection allowance more precisely, comparing this PR's approach against the User-Agent change proposed in #47 (marketplace) and #51 (notarized).

old = akd/1.0 CFNetwork/978.0.7 Darwin/18.7.0                  (current)
new = AuthKit/1 (Macintosh; OS X 26.5.2) (com.apple.dt.Xcode/26.0)   (#47 / #51)

6 requests down one connection, 3 trials each:

old   200 503 503 503 503 503
old   200 503 503 503 503 503
old   200 200 503 503 503 503     allowance 1-2, varies
new   200 200 200 200 503 503     allowance 4

So #47/#51 does raise the allowance, but the current string sometimes allows only one request, not two.

20 requests each on their own fresh connection, current string: 20/20 200, no failures. I could not reproduce the ~20-25% fresh-connection failure rate cited in #51; it may be IP or account specific.

That is why this PR uses a connection per request rather than raising the allowance: it holds whatever Apple sets the limit to, and needs no User-Agent change.

@CircuitSerein

Copy link
Copy Markdown

Independent confirmation of both the diagnosis and the fix, from a separate machine, network and Apple ID.

Reproduced the connection allowance with an intentionally invalid account name (probe-not-a-real-account@example.invalid), so no credentials are involved and Apple answers every request with an ordinary plist error rather than anything account-specific. Same result: requests 1 and 2 on a connection return 200 text/x-xml-plist, request 3 and every later one returns 503 text/html, and curl reports num_connects=0 for the reused ones. o=apptokens sent first on a fresh connection returns a normal plist, and four requests down one connection to developerservices2.apple.com are all 200, so this is specific to the gsa.apple.com edge and not to any endpoint, account or anisette state. Details in altstoreio/AltStore#1782.

Applied the same change — one NSURLSession per GrandSlam request, invalidated in the completion handler — as an injected hook rather than a source build, on macOS 27.0 (26A5421a), AltServer 1.7.2 (build 90), iPhone on iOS 27, free Apple ID. Sign-in, viewDeveloper, listTeams, addDevice, certificate issuance, app ID creation, provisioning profile download and app installation all succeed. Before the change, apptokens returned the HTML 503 on every attempt.

One data point worth adding for the AltStore side: the identical hook is needed inside AltStoreCore.framework. With it injected into AltStore.app, signing in on the device and installing an .ipa from within AltStore work again; without it, an AltServer-only fix leaves on-device sign-in failing with the same 3840, which is what several reports in #1782 are hitting.

Also worth separating out: the stale Xcode/3594.4.19 client identity from altstoreio/AltStore#1772 is a real inconsistency, but it is not this bug. With a coherent <Mac17,2> <macOS;27.0;26A5421a> <com.apple.AuthKit/1 (com.apple.dt.Xcode/25183.54.10)> string the apptokens request still returned the HTML 503 until connections stopped being reused — consistent with the measurement above showing #47/#51 only raises the allowance rather than removing the limit.

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.

2 participants