diff --git a/apps/desktop/scripts/notarize.mjs b/apps/desktop/scripts/notarize.mjs index e8335eadc4..0d0a39e2b4 100644 --- a/apps/desktop/scripts/notarize.mjs +++ b/apps/desktop/scripts/notarize.mjs @@ -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 diff --git a/tests-js/notarize.test.mjs b/tests-js/notarize.test.mjs index 44f76d6f4c..0786f1dfed 100644 --- a/tests-js/notarize.test.mjs +++ b/tests-js/notarize.test.mjs @@ -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) {