大多数 Capacitor CI/CD 失败都源于同一份简短清单:iOS code 签名、缺失或过时的 macOS 工具链、未进入原生项目的 Web 构建、重复使用的构建号,以及仅在上传时才暴露的商店要求。每个问题都有已知原因和一次性的修复方案。本指南按流水线阶段对这些陷阱进行分类,并说明你会看到的错误、发生原因以及需要修改的内容。
如果你正在从零开始搭建流水线,请先阅读 为 Capacitor 应用设置 CI/CD ,然后使用此清单来加固它。
阶段 1:构建 Web 层
坑点:原生应用打包了旧的 Web 构建
现象: CI 成功,应用已安装,但显示的是昨天的 UI。
原因: cap sync 会复制 webDir 在那一刻的内容。如果 pipeline 运行 cap sync 在 Web 构建之前,或者 Web 构建写入的文件夹与 webDir 中的 capacitor.config.ts不一致,会导致原生项目残留旧文件。
修复: 务必按此顺序执行步骤,输出文件夹为空就直接失败。
bun install --frozen-lockfile
bun run build
test -f dist/index.html || { echo "web build missing"; exit 1; }
bunx cap sync
请确保 webDir 与 bundler 输出路径一致(dist 针对 Vite, www 针对使用 Ionic 的 Angular, build 以及部分 React 配置)。
坑点:配置中残留 dev server URL
现象: 发布构建显示空白屏幕或尝试加载 http://192.168.x.x:5173.
原因: server.url 中的 capacitor.config.ts 是为热重载设置的,却被提交了。
修复: 切勿提交 server.url。请从 CI 从不设置的环境变量中读取:
import type { CapacitorConfig } from '@capacitor/cli'
const config: CapacitorConfig = {
appId: 'com.example.app',
appName: 'Example',
webDir: 'dist',
...(process.env.LIVE_RELOAD_URL && {
server: { url: process.env.LIVE_RELOAD_URL, cleartext: true },
}),
}
export default config
坑点:环境变量在错误时机被固化
现象: 生产环境应用连接的是 staging 环境的 API。
原因: Vite、webpack 和 Angular 会在构建时内联环境变量。构建时存在的值 bun run build 会被固化到二进制文件中,以及由同一 job 构建的任何 live update 中。
修复: 请在每个 job 的 web 构建前设置特定环境的环境变量,并为 staging 和 production 分别构建 bundle。不要试图在 cap sync.
坑点:lockfile 版本漂移
现象: CI 中的插件版本与本地机器不一致时,原生编译会因缺少符号而失败。
修复: 提交 lockfile 并使用以下命令安装: bun install --frozen-lockfile (或 npm ci)。固定 @capacitor/core, @capacitor/ios, @capacitor/android,以及 @capacitor/cli 到相同版本。版本不匹配是原生错误的常见原因;参见 修复 Capacitor 版本不匹配错误.
阶段 2:原生工具链
坑点:Node、JDK 或 Xcode 版本错误
现象: Unsupported class file major version, The engine "node" is incompatible,或 Xcode 报出与 SDK 功能相关的错误。
原因: 托管 runner 镜像会变化,且 Capacitor 8 有硬性最低要求。
| 工具 | Capacitor 8 版本要求 |
|---|---|
| Node.js | 22 或更高版本 |
| JDK | 21 |
| Xcode 的 Mac | 26 或更高版本 |
| iOS 部署目标 | 15.0 |
Android minSdkVersion / targetSdkVersion |
24 / 36 |
修复: 在 pipeline 中显式固定每个版本,而不是依赖 latest:
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '26'
坑点:使用 Apple 已不再接受的 Xcode 版本构建
现象: 上传失败,提示该应用使用了不受支持的 SDK 构建。
原因: 自 2026 年 4 月 28 日起,App Store Connect 要求使用 Xcode 26 和 iOS 26 SDK。自托管 Mac 和旧版 runner 镜像仍默认使用 Xcode 16。
修复: 选择一个 macos-26 镜像,或在 runner 上安装 Xcode 26。详情请参阅 Apple 对 Capacitor 应用的 Xcode 26 要求.
坑点:CocoaPods 与 SPM 混淆
现象: xcodebuild: error: 'App.xcworkspace' does not exist,或者找不到 pods。
原因: 新的 Capacitor 8 项目使用 Swift Package Manager 并构建 ios/App/App.xcodeproj。旧项目使用 CocoaPods 并构建 ios/App/App.xcworkspace 之后 pod install。从旧教程复制的 Pipeline 会假定使用 workspace。
修复: 检查项目使用的是哪一种,并构建正确的文件。如果正在迁移,请参阅 如何将您的 Capacitor 应用迁移到 SPM.
坑点:Gradle 升级后 Android 插件构建失败
现象: Namespace not specified, package attribute is deprecated,或来自插件的 build.gradle 在更新 Android Studio 之后。
修复: 在 android/build.gradle中锁定 Android Gradle Plugin 版本,在分支上升级,并优先更新插件。具体错误在 修复 AGP 9 中的 Capacitor 插件构建错误.
阶段 3:Code 签名
陷阱:iOS 签名仅在你的 Mac 上有效
现象: No signing certificate "iOS Distribution" found, No profiles for 'com.example.app' were found,或 errSecInternalComponent.
原因: 你的 Mac keychain 里存着证书,Xcode 会替你下载 profile。CI runner 两者都没有,而且它的 keychain 在非交互式会话中是锁定的。
修复: 将证书导入一个临时且已解锁的 keychain,再把 profile 安装到 Xcode 16 及更高版本会查找的位置:
security create-keychain -p "$KEYCHAIN_PASSWORD" ci.keychain
security set-keychain-settings -lut 21600 ci.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" ci.keychain
security import dist.p12 -k ci.keychain -P "$P12_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" ci.keychain
security list-keychains -d user -s ci.keychain login.keychain
PROFILES="$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles"
mkdir -p "$PROFILES"
cp app.mobileprovision "$PROFILES/"
使用 identity 名称 Apple Distribution,而不是旧版的 iOS Distribution。fastlane 的 setup_ci 加 match 能自动完成这件事。Capgo Build 把证书和 profile 作为环境变量接收,并在自己的机器上处理 keychain 操作,因此 Linux job 完全不用接触 security.
坑点:证书过期或被吊销
现象: 跑了一年的 pipeline 一夜之间就失败了。
原因: Apple 分发证书和描述文件会在一年后过期。队友在 Xcode 中创建新证书,也可能让 CI 使用的描述文件失效。
修复: 把过期日期加到团队日历里,为 CI 保留一个共享分发证书,并在构建前检查。 bunx @capgo/cli@latest build prescan --platform ios 上传任何内容前,检查证书有效期、密码和描述文件配对。
坑点:Apple ID 登录和双重认证提示
现象: fastlane 会卡住,等待 6 位 code。
修复: 使用 App Store Connect API 密钥(.p8key ID、issuer ID)代替 Apple ID 和密码。它不需要双重认证,并且权限可限定为 App Manager。如果密钥正确但认证仍然失败,请检查 runner 的时钟:token 使用本地时间签名,Apple 会拒绝时间戳有偏差的 token。
坑点:base64 密钥无法解码
现象: MAC verification failed, invalid keystore format,或 base64: invalid input.
原因: 复制 base64 值时带入了换行符,或者 .p12 使用 OpenSSL 3 默认设置创建,macOS 无法读取。
修复: 编码成一行,并使用 -legacy 创建时使用 .p12 使用 OpenSSL 3:
base64 -i dist.p12 | tr -d '\n' > dist.p12.b64
openssl pkcs12 -export -legacy -inkey key.pem -in cert.pem -out dist.p12
坑点:Android keystore 丢失
现象: 由于没人持有 keystore,你无法为更新签名。
修复: 使用 Play App Signing 时,keystore 就是上传密钥,Play Console 支持团队可以注册新的上传密钥。请把 keystore 存到 CI secrets 和离线备份中。别让它只留在一台笔记本上。 Android keystore 生成器 如果你从零开始,它会新建一个。
阶段 4:Pipeline 设计
坑点:每个 Pull Request 都跑 Native Build
现象: PR 检查缓慢,macOS 分钟数账单高昂。
原因: 在托管 macOS runner 上跑 iOS 构建,是大多数 CI 套餐里最贵的分钟数,而大多数提交只改 JavaScript。
修复: 每个 PR 都运行 lint、测试和 web build。在 release tag 或合并到 main时运行 native build。PR 预览时,将 web bundle 发布到 Capgo channel,而不是构建二进制文件,如 Capacitor 应用的 CI/CD 平台对比.
坑点:串行构建 iOS 和 Android
修复: 所述。用 matrix 让两个平台并行构建,并设置 fail-fast: false ,这样 iOS 签名问题就不会取消正常的 Android 构建。
strategy:
fail-fast: false
matrix:
platform: [ios, android]
坑点:未配置缓存或缓存配置错误
现象: 每次构建都要从头下载 Gradle 依赖和 CocoaPods,或者 staging 构建会因缓存过期而发布生产配置。
修复: 缓存 ~/.gradle/caches, ~/.gradle/wrapper,以及 ios/App/Pods 基于 lockfiles 进行键控。按环境划分缓存。使用 Capgo Build,可以通过以下方式拆分每个应用的构建缓存: --cache-key prod 以及 --cache-key staging,或通过以下方式跳过 --no-cache 以进行 clean build。
坑点:Monorepo 路径问题
现象: could not find capacitor.config 或原生项目中缺失的插件。
修复: 从 app 包中运行 Capacitor 命令,并让工具指向提升后的 node_modules。Capgo CLI 支持 --path 以及 --node-modules 来实现这一点。
阶段 5:提交至应用商店
坑点:重复使用 Build Number
现象: 在 iOS 上会提示“The bundle version must be higher than the previously uploaded version”,或在 Google Play 上提示“Version code has already been used”。
修复: 在 CI 中生成版本号。使用 fastlane 时,读取最新的 TestFlight build 并加 1。使用 Capgo Build 时这是默认行为:它会从 App Store Connect 获取最新 build number,或取 Google Play 上最高 versionCode 版本号并递增。
坑点:构建卡在 TestFlight 中
现象: upload 成功,但测试人员始终看不到 build。
原因: 缺少 export compliance 答案。
修复: 只需在...中声明一次 ios/App/App/Info.plist 如果你只使用标准加密:
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
坑点:隐私清单被拒
现象: 来自 Apple 的邮件,指出缺少必需的 reason API 声明 (ITMS-91053)。
修复: 添加一个 PrivacyInfo.xcprivacy 到 app target,并更新自带隐私清单的插件。请参阅 iOS 应用隐私清单指南.
坑点:构建产物类型错误
修复: Google Play 需要 AAB (bundleRelease),而不是 APK。 bundleRelease 仍然会成功,但如果没有 release 签名配置,会生成一个被 Play 拒绝的未签名 AAB,因此请配置 signingConfigs.release 中的 android/app/build.gradle 。iOS 需要 App Store 导出,而不是开发版或 ad hoc IPA。请检查构建步骤中的导出方式。
第 6 阶段:Live Updates
坑点:发布一个需要新原生构建的 live update
现象: OTA 更新后,应用调用已安装二进制中不存在的插件方法时会崩溃。
原因: web bundle 依赖的插件版本比用户设备上应用内编译的版本更新。
修复: 让 pipeline 来决定。 build needed 当原生依赖与 channel 上 Live Update 的内容匹配时退出码为 0,需要新二进制文件时为 1:
if bunx @capgo/cli@latest build needed com.example.app --channel production; then
bunx @capgo/cli@latest bundle upload com.example.app --channel production
else
bunx cap sync
bunx @capgo/cli@latest build request com.example.app --platform ios
bunx @capgo/cli@latest build request com.example.app --platform android
fi
当以下文件下的内容变更时,也强制走原生构建路径: ios/, android/,或 capacitor.config.* 变更。完整模式见 自动选择 live update 或 Native Build,兼容性规则见 原生兼容性.
消除最多陷阱的修复方案
如果只改一件事,请将 iOS 编译和签名从 CI 运行器中移出。当 Linux 任务将准备好的项目交给 Capgo 构建:
bun install --frozen-lockfile && bun run build
bunx cap sync ios
bunx @capgo/cli@latest build request com.example.app --platform ios --build-mode release
阶段 1、5 和 6 中的陷阱仍然适用,因为它们与项目本身有关,而非运行器。有关调试失败任务的更多信息,请参阅 修复 Capacitor CI/CD 流水线中的构建失败.
快速参考
| 错误 | 陷阱 | 章节 |
|---|---|---|
| 新构建中的旧 UI | cap sync Web 构建前 |
阶段 1 |
No profiles for ... were found |
配置文件未安装或不匹配 | 阶段 3 |
errSecInternalComponent |
钥匙串已锁定 | 阶段 3 |
MAC verification failed |
密码错误或 OpenSSL 3 问题 .p12 |
阶段 3 |
Unsupported class file major version |
JDK 版本错误 | 阶段 2 |
| 上传时 SDK 版本过旧 | Xcode 版本低于 26 | 阶段 2 |
| Bundle 版本必须更高 | Build number 重复 | 阶段 5 |
| 版本 code 已被使用 | 重复使用 versionCode |
阶段 5 |
| live update 后崩溃 | 原生变更通过空中推送发布 | 阶段 6 |