@@ -415,8 +415,14 @@ internal final class EtcdHttpClient: @unchecked Sendable {
415415 let _: EtcdStatusResponse = try await post ( path: apiPath ( " maintenance/status " ) , body: EmptyBody ( ) )
416416 }
417417
418+ /// Probes etcd gateway prefixes in order and selects the first that responds
419+ /// with a non-404 status. Covers all etcd versions:
420+ /// 3.5+ → /v3/ only
421+ /// 3.4 → /v3/ + /v3beta/
422+ /// 3.3 → /v3beta/ + /v3alpha/
423+ /// 3.2- → /v3alpha/ only
418424 private func detectApiPrefix( ) async throws {
419- let candidates = [ " v3 " , " v3beta " ]
425+ let candidates = [ " v3 " , " v3beta " , " v3alpha " ]
420426
421427 lock. lock ( )
422428 guard let session else {
@@ -439,7 +445,8 @@ internal final class EtcdHttpClient: @unchecked Sendable {
439445 do {
440446 ( _, response) = try await session. data ( for: request)
441447 } catch {
442- throw EtcdError . connectionFailed ( error. localizedDescription)
448+ // Network-level failure — server is unreachable regardless of prefix
449+ throw error
443450 }
444451
445452 guard let httpResponse = response as? HTTPURLResponse else {
@@ -450,16 +457,17 @@ internal final class EtcdHttpClient: @unchecked Sendable {
450457 continue
451458 }
452459
460+ // Any non-404 (200, 401, etc.) means this prefix exists on the server
453461 lock. lock ( )
454462 apiPrefix = candidate
455463 lock. unlock ( )
456464 Self . logger. debug ( " Detected etcd API prefix: \( candidate) " )
457465 return
458466 }
459467
460- lock . lock ( )
461- apiPrefix = " v3 "
462- lock . unlock ( )
468+ throw EtcdError . connectionFailed (
469+ " No supported etcd API found (tried: \( candidates . joined ( separator : " , " ) ) ) "
470+ )
463471 }
464472
465473 // MARK: - KV Operations
0 commit comments