From dfe4b97863834b9f09b241d253e1ea09fe74a662 Mon Sep 17 00:00:00 2001 From: AurionRodgerDiablo Date: Tue, 21 Jul 2026 19:29:36 +0200 Subject: [PATCH] HubClient: retry transient Hub tree-listing failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit listFiles treated any non-200 from the Hub tree API as variantNotFound, surfacing "No '' variant in @ — this model may not be published for this platform" even when the subtree exists. The Hub sheds load with transient 429s ("maximum queue size reached") and occasional 5xx; a single 429 on any of a multi-bundle model's subtree listings (OCR resolves vision/decoder/assets/tokenizer) failed the whole download with a misleading message. The large-file download path already retried 6x; only the tree listing did not. Retry the listing up to 5x with exponential backoff (2/4/8/16s) on 429/5xx. A firm 4xx (e.g. 404) still means the subtree really isn't published, so it keeps variantNotFound. Exhausting retries throws httpError(statusCode:) with the real code instead of the misleading "not published" message. Reproduced and verified fixed with mlboydaisuke/Unlimited-OCR-CoreAI via Examples/ReadDoc (swift run readdoc-cli --model unlimited-ocr). Claude-Session: https://claude.ai/code/session_01HyaY1ZCanqUvYbWVD4Rc53 --- Sources/CoreAIKitCore/HubClient.swift | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/Sources/CoreAIKitCore/HubClient.swift b/Sources/CoreAIKitCore/HubClient.swift index cc8cb21..b743380 100644 --- a/Sources/CoreAIKitCore/HubClient.swift +++ b/Sources/CoreAIKitCore/HubClient.swift @@ -32,9 +32,28 @@ struct HubClient: Sendable { else { throw CoreAIKitError.variantNotFound(repo: repo, path: path, revision: revision) } - let (data, resp) = try await URLSession.shared.data(from: api) - guard (resp as? HTTPURLResponse)?.statusCode == 200 else { - throw CoreAIKitError.variantNotFound(repo: repo, path: path, revision: revision) + // The Hub API sheds load with transient 429s ("maximum queue size reached") and + // occasional 5xx; retry those with backoff — only a firm 4xx means the subtree + // really isn't published. + var data = Data() + var status = 0 + for attempt in 0..<5 { + if attempt > 0 { + try await Task.sleep(nanoseconds: UInt64(1 << attempt) * 1_000_000_000) + } + try Task.checkCancellation() + let (d, resp) = try await URLSession.shared.data(from: api) + status = (resp as? HTTPURLResponse)?.statusCode ?? 0 + if status == 200 { + data = d + break + } + if (400..<500).contains(status), status != 429 { + throw CoreAIKitError.variantNotFound(repo: repo, path: path, revision: revision) + } + } + guard status == 200 else { + throw CoreAIKitError.httpError(statusCode: status, file: api.absoluteString) } struct TreeEntry: Decodable {