Summary
packages/apple-llm cannot be compiled for iOS with Xcode 26.2. AppleLLMImpl.swift calls SystemLanguageModel.default.tokenCount(for:), which is an iOS 26.4 SDK symbol. It does not exist in the iOS 26.2 SDK, so the package fails to build rather than degrading gracefully.
This effectively makes Xcode 26.4+ a hard requirement for any iOS build of the repo, including apps/expo-example, which is not stated anywhere in the README or CONTRIBUTING.md.
Error
❌ (../../packages/apple-llm/ios/AppleLLMImpl.swift:54:66)
52 | Task {
53 | do {
> 54 | let tokenCount = try await SystemLanguageModel.default.tokenCount(for: text)
| ^ value of type 'SystemLanguageModel' has no member 'tokenCount'
CommandError: Failed to build iOS project. "xcodebuild" exited with error code 65.
Confirming the symbol is absent from the 26.2 SDK entirely:
$ xcrun --sdk iphoneos --show-sdk-version
26.2
$ sdk=$(xcrun --sdk iphoneos --show-sdk-path)
$ grep -rl "tokenCount" "$sdk/System/Library/Frameworks/FoundationModels.framework/"
(no matches)
Why the availability guard doesn't help
The call is already wrapped in if #available(iOS 26.4, *) (AppleLLMImpl.swift:43):
#if canImport(FoundationModels)
if #available(iOS 26.4, *) {
guard SystemLanguageModel.default.availability == .available else { ... }
Task {
do {
let tokenCount = try await SystemLanguageModel.default.tokenCount(for: text)
resolve(tokenCount)
} catch { ... }
}
} else {
let error = AppleLLMError.unsupportedOS
reject("AppleLLM", error.localizedDescription, error)
}
#else
...
#endif
#available gates runtime dispatch — it lets a binary built against a newer SDK run safely on older OS versions. It cannot compile a symbol that the SDK being built against does not declare. #if canImport(FoundationModels) doesn't help either: the framework is importable in 26.2, it just lacks this one method.
Note this is specific to the 26.4 call. Every other gate in the file is iOS 26 / @available(iOS 26, *), which compiles fine against the 26.2 SDK.
Introduced in 9e14139 ("feat: graceful apple context window error handling, countTokens API", #212).
The JS layer already handles this case
src/AppleFoundationModels.ts treats the native method as optional and fails gracefully when it's missing:
countTokens?: (text: string) => Promise<number>
// ...
countTokens: (text) => {
if (typeof nativeAppleLLM.countTokens !== 'function') {
throw new Error(
'Apple Foundation Models token counting is unavailable. It requires iOS 26.4 or newer and a native build with countTokens support.'
)
}
return nativeAppleLLM.countTokens(text)
}
That error message explicitly anticipates "a native build with countTokens support" — i.e. the design already expects builds where the native side omits this method. The Swift side just doesn't implement that half of the contract; it hard-fails at compile time instead of being omitted.
(Minor inconsistency worth a look: the codegen spec at src/NativeAppleLLM.ts:39 declares countTokens(text: string): Promise<number> as required, while the wrapper above declares it optional.)
Suggested fix
Compile the 26.4 path conditionally on the SDK, so older Xcode versions produce a build without the method and the existing JS fallback handles it. One approach — have the podspec detect the SDK and define a flag:
sdk_version = `xcrun --sdk iphoneos --show-sdk-version`.strip
if Gem::Version.new(sdk_version) >= Gem::Version.new('26.4')
s.pod_target_xcconfig = {
'OTHER_SWIFT_FLAGS' => '$(inherited) -D APPLE_LLM_HAS_TOKEN_COUNT'
}
end
#if APPLE_LLM_HAS_TOKEN_COUNT
if #available(iOS 26.4, *) { /* existing implementation */ }
else { /* unsupportedOS */ }
#else
let error = AppleLLMError.unsupportedOS
reject("AppleLLM", error.localizedDescription, error)
#endif
Alternatively, if requiring Xcode 26.4+ is intentional, documenting it in the README and CONTRIBUTING.md prerequisites would be enough — right now the failure mode is an opaque Swift compile error partway through a long build.
Local workaround
Replacing the body of countTokens with the unsupportedOS rejection lets the package and the example app build and run normally on Xcode 26.2. Only the token-counter readout is lost; text generation, embeddings, transcription and speech are unaffected.
Why CI doesn't catch it
.github/workflows/ci.yml runs cd packages/apple-llm && bun run prepare on macos-latest, but that is a react-native-builder-bob JavaScript build — it never invokes xcodebuild, so the Swift in ios/ is never compiled. A build-only xcodebuild job against apps/expo-example (no device, no signing) would catch this class of problem.
This is the same gap reported in #226, where a second, unrelated defect also reaches main unbuilt.
Environment
- macOS 15 (Darwin 25.5.0), Apple Silicon
- Xcode 26.2, iOS SDK 26.2
- Target device: iPad, iOS 26.6 (the OS supports the API; the SDK is what's behind)
- Repo at d4ce05f
Summary
packages/apple-llmcannot be compiled for iOS with Xcode 26.2.AppleLLMImpl.swiftcallsSystemLanguageModel.default.tokenCount(for:), which is an iOS 26.4 SDK symbol. It does not exist in the iOS 26.2 SDK, so the package fails to build rather than degrading gracefully.This effectively makes Xcode 26.4+ a hard requirement for any iOS build of the repo, including
apps/expo-example, which is not stated anywhere in the README orCONTRIBUTING.md.Error
Confirming the symbol is absent from the 26.2 SDK entirely:
Why the availability guard doesn't help
The call is already wrapped in
if #available(iOS 26.4, *)(AppleLLMImpl.swift:43):#availablegates runtime dispatch — it lets a binary built against a newer SDK run safely on older OS versions. It cannot compile a symbol that the SDK being built against does not declare.#if canImport(FoundationModels)doesn't help either: the framework is importable in 26.2, it just lacks this one method.Note this is specific to the 26.4 call. Every other gate in the file is
iOS 26/@available(iOS 26, *), which compiles fine against the 26.2 SDK.Introduced in 9e14139 ("feat: graceful apple context window error handling, countTokens API", #212).
The JS layer already handles this case
src/AppleFoundationModels.tstreats the native method as optional and fails gracefully when it's missing:That error message explicitly anticipates "a native build with countTokens support" — i.e. the design already expects builds where the native side omits this method. The Swift side just doesn't implement that half of the contract; it hard-fails at compile time instead of being omitted.
(Minor inconsistency worth a look: the codegen spec at
src/NativeAppleLLM.ts:39declarescountTokens(text: string): Promise<number>as required, while the wrapper above declares it optional.)Suggested fix
Compile the 26.4 path conditionally on the SDK, so older Xcode versions produce a build without the method and the existing JS fallback handles it. One approach — have the podspec detect the SDK and define a flag:
Alternatively, if requiring Xcode 26.4+ is intentional, documenting it in the README and
CONTRIBUTING.mdprerequisites would be enough — right now the failure mode is an opaque Swift compile error partway through a long build.Local workaround
Replacing the body of
countTokenswith theunsupportedOSrejection lets the package and the example app build and run normally on Xcode 26.2. Only the token-counter readout is lost; text generation, embeddings, transcription and speech are unaffected.Why CI doesn't catch it
.github/workflows/ci.ymlrunscd packages/apple-llm && bun run prepareonmacos-latest, but that is areact-native-builder-bobJavaScript build — it never invokesxcodebuild, so the Swift inios/is never compiled. A build-onlyxcodebuildjob againstapps/expo-example(no device, no signing) would catch this class of problem.This is the same gap reported in #226, where a second, unrelated defect also reaches
mainunbuilt.Environment