refactor(desktop): one retry schedule and one timeout fixture

The missing-ticket retries used their own delay table, which matched
the first five lost-contact pauses. They now call contactDelay(), with
a limit of five. A ticket that never propagates still gets 6 staples,
the same pauses (15, 30, 60, 120 and 120 s) and the same error.

The LOST_CONTACT comment now says which codes point at the runner's own
network (-1003, -1004, -1006, -1009) rather than at Apple.

The older resume test's local `timeoutOutput` copied `notaryTimeout`,
apart from `_kCFStreamErrorDomainKey` (1 rather than 4, the value in
the canary log). The test now uses `notaryTimeout`.
This commit is contained in:
BearHuddleston committed 2026-11-03 20:04:19 +00:00
1 parent 23ca14a28e
commit 9dd1ea34f2
2 files changed
+8 -8

No files matched your search

+7 -6
View File
@@ -25,9 +25,10 @@ function inlineKeyLooksValid(value) {
return value.includes('BEGIN PRIVATE KEY') && value.includes('END PRIVATE KEY')
}
// Apple's notary and ticket-delivery services sometimes drop connections for many minutes. These
// NSURLError codes mean a request got no answer (timed out, connection lost, host unreachable), not a
// verdict, so the same request is repeated while the notarization budget lasts.
// These NSURLError codes mean a request to Apple got no answer: it timed out (-1001), its connection
// dropped (-1005), or the runner could not reach the host (-1003, -1004, -1006, -1009), which points at
// the runner's own network rather than at Apple. None of them is a verdict, so the same request is
// repeated within its time limit.
const LOST_CONTACT = /NSURLErrorDomain\s+Code=-(?:1001|1003|1004|1005|1006|1009)\b/
const CONTACT_DELAYS = [15000, 30000, 60000, 120000]
// Stapling starts after Apple has accepted the app. A runner that still cannot reach Apple's
@@ -126,7 +127,7 @@ async function submitAndStaple(zipPath, appPath, auth, { run, sleep, log, now })
}
// Accepted tickets can take time to reach Apple's CloudKit lookup service.
const delays = [15000, 30000, 60000, 120000, 120000]
const ticketRetryLimit = 5
const stapleDeadline = Math.min(deadline, now() + STAPLE_CONTACT_WINDOW)
let ticketRetries = 0
let contactRetries = 0
@@ -139,8 +140,8 @@ async function submitAndStaple(zipPath, appPath, auth, { run, sleep, log, now })
} catch (error) {
const output = commandOutput(error)
if (error.code === 65 && /CloudKit query[^\n]*Record not found/i.test(output)) {
if (ticketRetries === delays.length) throw error
const delay = delays[ticketRetries++]
if (ticketRetries === ticketRetryLimit) throw error
const delay = contactDelay(ticketRetries++)
log(`[notarize] accepted ticket not available. Retrying in ${delay / 1000}s.`)
await sleep(delay)
continue
+1 -2
View File
@@ -68,7 +68,6 @@ test('accepted submissions retry only ticket propagation, without resubmitting o
test('HTTP timeouts resume the same submission with notarytool wait and a shrinking budget', async () => {
const f = fixture()
// Replay the status-request failure from the macOS release log.
const timeoutOutput = `Error: HTTPError(statusCode: nil, error: Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo={NSErrorFailingURLStringKey=https://appstoreconnect.apple.com/notary/v2/submissions/${submissionId}?, _kCFStreamErrorDomainKey=1})`
try {
for (const env of f.environments) {
const calls = []
@@ -81,7 +80,7 @@ test('HTTP timeouts resume the same submission with notarytool wait and a shrink
if (args[0] === 'notarytool' && args[1] === 'submit') {
elapsed += 10 * 60 * 1000
return runCommand(process.execPath, ['-e',
'process.stderr.write(process.argv[1]); process.exitCode = 1', timeoutOutput])
'process.stderr.write(process.argv[1]); process.exitCode = 1', notaryTimeout])
}
if (args[0] === 'notarytool' && args[1] === 'wait') {
if (++waits === 1) {