那天晚上,app 已经写完了。
模拟器里,一天的行动轨迹渲染成一串液态水滴:停留的地方是一颗颗半透明的圆,时间上相邻的停留之间有金属液滴一样的黏连,间隔越久颈缩越细,超过三小时就断开。最新的那颗是橙色的。挺好看的。三个 tab、后台采集、断网补传、告警 deeplink,全部在模拟器里验证过。这个 app 是给我自己那个位置追踪服务(上线记在这)配的客户端,服务端跑了快三个月了,就差一个正经的 iOS app。
最后一步:传 TestFlight。归档、导出、上传,脚本都写好了,一条命令的事。
然后 Xcode 说:No Accounts。
行吧,登录态丢了,重新登。登完,报错变成了 No Account for Team——账号登上了,但 Xcode 说这个 Apple ID 不属于我自己的 team。
我第一反应是登录态没同步,重启 Xcode、删缓存、重新登,都试了。没用。那就绕过 Xcode,直接看源头。
打开 developer.apple.com。首页大字:Enroll today。
就是说,在 Apple 看来,我根本不是付费开发者。
但奇怪的是,点进 Certificates, Identifiers & Profiles,报错不是”请先加入计划”,而是:
Unable to find a team with the given Team ID to which you belong.
更奇怪的是,App Store Connect 还能进。团队还在,我的角色还是 Account Holder,之前上架的 app 还躺在那。也就是说 ASC 认识我,developer portal 不认识我,同一个登录态。
再用 ASC API 从程序侧确认一遍。生成一个 App Store Connect API key,拿 ES256 签个 JWT 去打最普通的接口:
import jwt, time, urllib.request
token = jwt.encode( {"iss": ISSUER_ID, "aud": "appstoreconnect-v1", "exp": int(time.time()) + 600}, open(KEY_PATH).read(), algorithm="ES256", headers={"kid": KEY_ID},)req = urllib.request.Request( "https://api.appstoreconnect.apple.com/v1/bundleIds", headers={"Authorization": f"Bearer {token}"},)响应:
{ "errors": [{ "status": "403", "code": "FORBIDDEN.REQUIRED_AGREEMENTS_MISSING_OR_EXPIRED", "detail": "This request requires an in-effect agreement that has not been signed or has expired." }]}这个错误码的官方语义很明确:有协议没签或者过期了,去 App Store Connect 的 Agreements 页面签字就好。这是个常规问题,常规修法。
问题是,Agreements 页面上没有任何待签的协议。因为在协议系统看来,我压根不属于任何 team——没有 team,哪来的 team 协议给你签。
到这里,三个独立信号指向同一件事:Xcode、developer portal、ASC API,全都认为我和自己的 team 之间的成员关系断了。不是协议问题,是成员关系本身没了。
那成员关系是什么时候断的?我翻了账户的完整历史,时间线是这样的:
- 3 月 17 日:购买 Apple Developer Program,$99。
- 3 月 19 日:误操作又买了一次,$99。
- 3 月 26 日:Apple 对其中一笔退款。
- 3 月 24 日 / 3 月 31 日:Paid Applications Agreement、Program License Agreement 先后签署——协议层一切正常。
- 3 月 31 日:提交了一个 app 审核,收到审核反馈。
- 4 月 2 日:Xcode Cloud 构建成功。
- 8 月 30 日:发现一切都断了。
我看到”3 月 26 日退款”的第一反应是:完了,退款把 membership 一起退没了。这个结论看起来太顺了——顺到我差点就写进结论里。
但它是错的。退款是 3 月 26 日,而 3 月 31 日还在签协议、提审 app,4 月 2 日 Xcode Cloud 还是绿的。退款之后 membership 至少正常工作了一个星期。 断裂发生在 4 月到 8 月之间的某个时刻,没有任何邮件、任何通知。
(这个教训值得单独记一笔:时间线上两件事相邻,不等于因果。差一步就把一个错误结论写死了。)
好,membership 出问题了,那大不了重新买一次?$99 而已,先解除阻塞再说。
点 Enroll,走流程,然后被重定向到一个 URL 里带着 /enroll/ineligible/account-holder 的页面:
Your Apple Account is already associated with the Account Holder of a membership.
我盯着这句话看了半天。
你不能重新买会员,因为你已经是会员的 Account Holder 了。
也就是说:Apple 的记账层认为我的 membership 存在、我是 Account Holder;Apple 的 team 关联层认为我不属于任何 team。两个系统对同一个账户给出了互相矛盾的答案,而我卡在中间——既用不了,也买不回。
这就好比户口系统把你的户口弄丢了,你去补办,窗口说”查无此人”;你说那我重新落户总行吧,窗口说”不行,系统显示你已有户口”。
到这一步,用户侧能做的所有事都做完了。这不是配置问题、不是协议问题、不是付款问题,是 Apple 内部两个数据存储之间的不一致,只有 Apple 自己能修。
我去网上找了一圈先例。几个发现:
一,“Unable to find a team with the given Team ID” 这个报错在 Apple Developer Forums 上有至少六七个独立 thread。Apple DTS 工程师确认过一个常见根因:Individual 类型账户下,被邀请的”团队成员”不算真正的 team 成员,访问证书页就会报这个错。但我是 Account Holder 本人,不是被邀请的成员,这个根因套不上。
二,Hacker News 上有一个案例,症状和我几乎逐字相同:账户活跃了大半年,某天突然失去 developer portal 访问权(ASC 仍能进),重新 enroll 时报同一句 “already associated with the Account Holder of a membership”。那位老哥耗了两个多月、15 封邮件、10 通电话,thread 结束时还没解决。n=1,不能外推,但至少说明这个死锁形态不是我一个人遇到过。
三,Apple Developer Forums 有个 thread 标题就叫 “Charged twice for Apple Developer Program, membership still inactive, no response from support”,时间是 2026 年 3 月底——和我的购买、误重复购买、退款几乎同期。场景本质一致:重复扣款之后 membership 状态卡死。但那个 thread 同样没有下文,Apple 也从没公开确认过”退款流程会误伤 membership”这条因果链。所以严格说,“3 月那次退款在几个月后异步触发了 termination”只是我目前最合理的猜测,不是结论——它是唯一能把时间线串起来的解释,但没有任何一方证实过。
四,Apple 官方对这类问题的响应承诺是 1-2 个工作日。实际案例里,复杂的 membership 问题拖数周到数月的比比皆是,中间穿插”operations team 仍在处理”式的模板回复。
于是走正规渠道:developer.apple.com/contact,Membership and Account 分类,提交了一份完整的英文工单——时间线、两笔订单号、退款凭证号、三层报错原文,全部附上。几分钟后收到自动确认邮件,拿到 case 号。
现在就是等。
回头看这件事,最让我在意的不是 $99 或者拖延,而是故障的形态。分布式系统里我们太熟悉这种病了:两个服务各自维护一份关于同一实体的状态,一个异步流程(退款?对账?某个批处理?)改了其中一份,另一份没跟上,也没有任何对账机制去发现这个矛盾。放在自己的系统里,这叫数据不一致,写个 reconciliation job 就修了。放在 Apple 的系统里,它表现为:一个付了钱的开发者,在没有任何通知的情况下,被静默地开除出自己的 team,并且因为”已经是会员”而无法再次付钱。
而且这个故障对用户是完全不可自愈的。所有自助路径——签协议、重新登录、重新购买——都被那个不一致本身堵死了。唯一的出口是人工工单,而人工工单的实际 SLA 是”看运气”。
app 还在那。归档好的 .xcarchive、上传脚本、API key,全部就位,membership 恢复的那一刻,一条命令就能上 TestFlight。至于 app 本身——液态金属地图的 metaball 渲染、停留分组算法、后台采集那一堆坑——等它真的跑在真机上,再单独写一篇。
希望到时候这篇的结尾能补一行:Apple 回信了,修好了,用时 X 天。
以及希望 X 是个位数。