测试员发消息:“你这 beta 打不开了,提示 ‘This beta has expired’。” 你打开 App Store Connect → TestFlight → Builds,看到那个跑了两个月、一直好好的 build 现在显示 Expired。少数测试员还能再开一小会儿(二进制缓存在设备上,要到下一次 TestFlight 刷新才失效),但新安装和大部分老安装都被挡住了。整个外部测试周期就此暂停,而且根本没有”延长”过期 build 的按钮。
最快的修法: 过期的 build 救不回来——上传一个 build 号更高的新 build,再挂到同一个测试组。如果是同一个版本(比如还是 2.7),只是 build 号更高,通常几分钟就生效、不用重审。如果是新版本(2.7 → 2.8),新版本的第一个 build 要先过 Beta App Review——截至 2026 年 6 月,一般 24 小时内能过。
为什么 TestFlight build 会过期
TestFlight build 有从上传日起算的 90 天固定有效期,不是从第一次安装起算。Apple 官方文档明确写了这一点,且严格执行:没有账号级例外,也没有任何办法把 build 延到第 90 天之后。这个时钟纯粹看二进制的”年龄”——一到 90 天,App Store Connect 就把状态翻成 Expired,TestFlight App 拒绝启动并提示 “This beta has expired”。
这不是 bug,也不代表你的 App 出了什么问题。这是设计如此,唯一的修法就是用一个新的二进制替换掉过期的那个。
先判断你是哪种情况
动手前先把自己的情况对上号,再决定下一步。
| App Store Connect 里的现象 | 根因 | 最快修法 |
|---|---|---|
| Build 超过 90 天、状态 Expired、没有更新的 build | 从没轮换过新 build | 上传新 build(下面 Step 2) |
| 有更新的 build、状态 Ready to Submit,但测试员看不到 | 新 build 没挂进 group | 把它挂到 group(Step 5) |
| 更新的 build 状态 Waiting for Beta App Review | 新版本在排队审核 | 用内部测试兜底,等审核过 |
| 更新的 build 加了又被 withdrawn,测试员退回旧的 | 修回归 bug 时出现轮换缺口 | 重新挂一个没过期的 build,或上传新的 |
| 一个 group 的测试员看得到,另一个看不到 | 新 build 只挂到了部分 group | 把 build 加到每个活跃 group |
1. Build 到了 90 天 TestFlight 生命周期上限
90 天时钟从上传起算。90 天后 Apple 标 build 为 Expired,TestFlight App 拒绝启动。
如何判断:App Store Connect → TestFlight → Builds,打开这个 build 看它的上传日期。85+ 天前的就快过期或已过期。同一页面会为每个 build 显示明确的到期日。
2. 过期前没传新 build
你为一个长跑 beta 只传过一个 build,从不轮换。第 91 天 build 过期,测试员被锁在外面,你手头也没有备用 build。
如何判断:在 TestFlight → Builds 列表里数还在 90 天有效期内的 build 有几个。只有一个、还很旧,就有风险。
3. 留着旧 build,同时把新 build 撤回了
你传了新 build 挂出去,随后发现回归 bug,把它从 group 撤了。测试员退回老 build,而老 build 在你修 bug 期间过期了。
如何判断:这个 build 的 Activity / 版本历史里有近期的 “withdrawn” 动作。如果当前唯一可用的 build 超过 60 天,就有轮换缺口。
4. 新 build 传了但没挂上测试组
你传了新 build,处理完了,在 Builds 里显示 Ready to Submit——但你一直没把它加进外部测试 group,也没发给内部测试员。旧的过期了,测试员看不到新的。
如何判断:TestFlight → 你的 group,查挂着的 build 列表。最新 build 不在 group 里就把它挂上。
5. 新 build 在等 Beta App Review
你及时上传了,但新版本的第一个 build 在 Beta App Review 排队。等待期间旧 build 过期,测试员两个都看不到。
如何判断:新 build 状态 Waiting for Beta App Review。用内部测试兜底(不用审核),或者等审核过。
需要收集的信息
- 过期 build 的上传日期(算 90 天用)。
- 过期 build 的版本(
CFBundleShortVersionString)和 build 号(CFBundleVersion)。 - 任何 pending build 的状态(Processing、Waiting for Beta App Review、Ready to Submit)。
- 过期 build 和 pending build 各自的 group 挂载状态。
- 测试员投诉时间戳(如果你要沟通修复时间)。
最短修复路径
Step 1:找对”下一个”build
App Store Connect → TestFlight → Builds 里找:
- 已处理、版本
>=过期 build、且 build 号更高的。 - 没有就要上一个新的。
不能复活过期 build,只能替换。
Step 2:打一个新 archive
同一版本里每个 build 都需要一个唯一且更高的 build 号——同版本内重复的 CFBundleVersion 会被 Apple 拒绝或悄悄丢掉。在 Xcode 里:
- 递增
CFBundleVersion(如47→48)——必须,因为 build47已经在 Apple 记录里了。 - 这是真功能更新就一并 bump
CFBundleShortVersionString。 - Product → Clean Build Folder。
- Product → Archive(destination 选 Generic iOS Device / “Any iOS Device”)。
归档并上传到 App Store Connect 需要较新的 Xcode 和 SDK——截至 2026 年 6 月,Apple 要求用 Xcode 26 / iOS 26 SDK 或更新版本构建(这条下限于 2026 年 4 月 28 日生效)。Apple 会不定期抬高这个最低要求,更老的 Xcode/SDK 构建在上传时会被拒。
或者用命令行:
agvtool next-version -all # bump build 号
xcodebuild -workspace Acme.xcworkspace -scheme Acme \
-archivePath build/Acme.xcarchive archive
xcodebuild -exportArchive -archivePath build/Acme.xcarchive \
-exportPath build/ipa -exportOptionsPlist exportOptions.plist
# 上传:altool --upload-app 对 App Store / TestFlight 仍然可用,
# 但 Apple 现在更推荐用较新的工具链或 Transporter App。
xcrun altool --upload-app -f build/ipa/Acme.ipa \
-t ios --apiKey "$ASC_KEY_ID" --apiIssuer "$ASC_ISSUER_ID"
优先用 App Store Connect API key 参数(--apiKey / --apiIssuer)而不是 Apple ID 密码,因为 app 专用密码一旦轮换就会失效。如果 altool 报错,免费的 Transporter App(Mac App Store)是拖拽上传的替代方案。
Step 3:等处理完成
Apple 在 build 可用前要先处理它——通常 5-30 分钟,繁忙时段偶尔更久。状态走 Processing → Ready to Submit(挂上并审核通过后切 Ready to Test)。
# 可选:轮询 App Store Connect API 看处理状态
curl -H "Authorization: Bearer $JWT" \
"https://api.appstoreconnect.apple.com/v1/builds?filter[app]=$APP_ID&sort=-uploadedDate" \
| jq '.data[0].attributes.processingState'
processingState 会先返回 PROCESSING,build 就绪后变成 VALID。
Step 4:提 Beta App Review(仅外部测试)
新版本的第一个 build 必须过 Beta App Review,外部测试员才能装。同一版本下的新 build 号通常跳过审核、自动生效。内部测试(最多 100 个 App Store Connect 团队成员)从不需要审核。
App Store Connect → TestFlight → 你的外部 group → 选中这个 build → Submit for Beta App Review。截至 2026 年 6 月,Apple 约 90% 的提交在 24 小时内审完,所以这一步很少是瓶颈。
Step 5:挂上测试组
build 变 Ready to Test 后:
- TestFlight → 你的外部 group → Builds → ”+” → 选新 build。
- TestFlight → Internal Testing → ”+” → 选新 build(不用审核,处理完几乎立即可用)。
要把它加到每一个活跃 group,不只一个——没拿到新 build 的 group 里,测试员看到的还是那个过期的旧 build。
Step 6:通知测试员
在 TestFlight → 你的外部 group 里点 Notify Testers。这会给测试员发邮件,附带跳转更新的深链。
填好 What to Test 发布说明(这是测试员在 TestFlight App 里看到的字段)。把替换专属信息写上,比如 “请更新——上一个 build 已过期,打不开了。“
怎么确认已经修好
- 过期 build 状态保持 Expired,新 build 显示 Ready to Test 或 Available。
- 通过 TestFlight App 更新的测试员能看到新 build 的 “What to Test” 说明。
- 用 TestFlight 邀请链接在干净设备上冷装,拿到的是新 build 而不是过期的那个。
- 新 build 的到期日(App Store Connect 可见)正好是上传日 +90 天。
如果还是没修好
- 测试员看不到新 build:核查它挂到了对的 group,且状态是 Ready to Test(不是还在 Waiting for Beta App Review 或只到 Ready to Submit)。
- TestFlight 缓存没刷:让测试员强退并重开 TestFlight,或登出再登入。在 App 的 TestFlight 页面下拉刷新也有用。
- 进错 group:在没挂新 build 的 group 里的测试员,看到的还是过期的旧 build。逐个核对测试员的 group 归属。
- 上传因 build 号被拒:如果
altool/Xcode 拒了上传,说明新的CFBundleVersion没有高于该版本下已有的 build 号——再 bump 一次。 - 替换 build 的 Beta 审核异常慢:看 Beta App Review 太慢。
常见问题
能延长或重新激活过期的 TestFlight build 吗? 不能。90 天上限是固定的、绑在上传日上,没有账号级例外。唯一出路是上传一个替换 build。
为什么有些测试员还能打开过期的 build? 二进制缓存在设备上,所以已经打开过的测试员在过期后还能再开一小段时间。等 TestFlight 下次刷新,就会用 “This beta has expired” 挡住启动。新安装则是立即被挡。
我需要换一个全新版本号,还是只要换 build 号?
只要 build 号更高就足以替换过期 build。同版本下的新 build 通常跳过 Beta App Review,生效最快。只有真做了功能更新才去 bump 版本号(CFBundleShortVersionString)。
替换 build 一定要走 Beta App Review 吗? 只有新版本的第一个 build 要。同版本的后续 build、以及所有内部测试 build,都跳过审核,处理完就可用。
测试员多久能装上替换 build? 处理通常 5-30 分钟。如果要审核,再加最多约 24 小时(多数更快)。同版本的内部测试 build 一般几分钟内就能测。
预防建议
- 任何长跑 beta,在上传日 +80 天加一个日历提醒。
- 维护”轮换”节奏:至少每 60 天传一个新 TestFlight build,即使代码改动很小。
- 活跃外部 group 始终挂着至少一个 60 天内的 build。
- 每轮测试都 bump build 号,让向前滚动成为机械操作。
- 在
BETA.md里记录 TestFlight 轮换流程,别把责任只压在一个人身上。