From 9dd1ea34f23efb36ef8e32ee4ffa02eb95f0d268 Mon Sep 17 00:00:00 2001 From: BearHuddleston Date: Tue, 3 Nov 2026 20:04:19 +0000 Subject: [PATCH] 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`. --- apps/desktop/scripts/notarize.mjs | 13 +++++++------ tests-js/notarize.test.mjs | 3 +-- 2 files changed, 8 insertions(+), 8 deletions(-) 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) {