惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

腾讯CDC
博客园 - Franky
MyScale Blog
MyScale Blog
L
LangChain Blog
Martin Fowler
Martin Fowler
Recent Announcements
Recent Announcements
Stack Overflow Blog
Stack Overflow Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 司徒正美
量子位
A
About on SuperTechFans
C
Check Point Blog
大猫的无限游戏
大猫的无限游戏
Last Week in AI
Last Week in AI
小众软件
小众软件
Apple Machine Learning Research
Apple Machine Learning Research
I
InfoQ
V
Visual Studio Blog
Vercel News
Vercel News
B
Blog
爱范儿
爱范儿
aimingoo的专栏
aimingoo的专栏
U
Unit 42

rxliuli blog

Safari 扩展 Popup 中的滚动迟缓问题 Safari 云签名在 GitHub Actions 中的两个陷阱 使用 GitHub Actions 全自动发布 Safari 扩展 旅行 - 东福山岛 旅行 - 新加坡 - 越南 旅行 - 马来西亚 - 下 旅行 - 马来西亚 - 上 Browser Extension Dev - 08. 发布 Chrome Web Store Browser Extension Dev - 07. Popup UI Browser Extension Dev - 06. 按需注入脚本 Browser Extension Dev - 05. 存储和配置 Browser Extension Dev Extra - User Script 介绍 Browser Extension Dev - 04. Background Script Browser Extension Dev Extra - 如何找到锁定滚动的元素 Browser Extension Dev - 03. 注入 UI Browser Extension Dev - 02. 使用 WXT Browser Extension Dev - 01. 介绍基本概念 Chrome => Firefox 扩展移植的那些坑 发布 Safari 扩展到 iOS 应用商店 再游新疆 -- 自驾 实践: 使用 Hono 开发全栈应用 Web 流式写入文件 在构建时而非运行时编译 Markdown 在 Web 中解压大型 ZIP 并保持目录结构
App Store Connect 发布事故回顾
rxliuli · 2026-08-21 · via rxliuli blog

背景

今天在修复一个 Redirector 扩展 [1] 的一个错误之后,推送了一个版本触发 CI 构建并使用 extport [2] 全平台发布,在发布 extport 的时候出现了一个神秘的错误。

1
macos: submission item add failed (409): { "errors" : [ { "id" : "607f4cc4-96af-4d99-ae60-78bb87489e8a", "status" : "409", "code" : "STATE_ERROR.ENTITY_STATE_INVALID", "title" : "appStoreVersions with id '652e9da7-e899-4244-904f-3c1dccacef56' is not in valid state.", "detail" : "This resource cannot be reviewed, pleas…

提示我前往 App Store Connect [3] 查看,发现是提交版本时有些必填的信息没有填写,而之前从未需要填写过。包括

1. App Information > Age Ratings 里面增加了新的 Social Media 必填项
Snipaste\_2026-08-21\_18-49-10.png

2. inflight > Contact Information 以前没填也能提交,现在会被卡
Snipaste\_2026-08-21\_18-48-43.png

当然,即使在我手动修复了这个问题之后,也仍然无法在 extport 进行发布重试。我修改 extport 的逻辑使其更加健壮,同时也在文档中记录了此次事故 [4]。在手动完成重试之后,我不禁在思考:我有二十多个已经发布的 ios/macos app,我应该怎么批量修复它们避免下次类似事故的发生?

过程

我尝试使用 pi agent 并结合 chrome devtools mcp 来操控浏览器,原本计划是一个个点击页面并查看 ui 上的状态的,没想到 ds v4 flash 的操作要野的多,直接逆向 fetch 请求并使用 js 脚本重放,根本不通过操控 HTML 页面来查看和修改 App Store Connect 的信息。例如这段代码可以直接粘贴到 app store connect 控制台来查看哪些 app 缺少必填信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// 检查所有 app 的 Age Ratings(Social Media)和 inflight 版本的 Contact Information
// 只读,不改任何东西。用法:浏览器登录 appstoreconnect.apple.com 后,F12 控制台粘贴执行

const API = 'https://appstoreconnect.apple.com/iris/v1'
const H = { Accept: 'application/json' }

// 编辑中的版本状态(inflight,能改字段)
const INFLIGHT = new Set([
'PREPARE_FOR_SUBMISSION',
'WAITING_FOR_REVIEW',
'IN_REVIEW',
'REJECTED',
'DEVELOPER_REJECTED',
'ACCEPTED',
'READY_FOR_REVIEW',
])

async function get(path) {
const res = await fetch(API + path, { credentials: 'include', headers: H })
if (!res.ok) throw new Error(res.status + ' ' + path)
return res.json()
}

;(async () => {
const { data: apps } = await get('/apps?limit=200')
const active = apps.filter((a) => !a.attributes.removed)
console.log('共 ' + active.length + ' 个活跃 app\n')

for (const app of active) {
console.log('== ' + app.attributes.name + ' [' + app.id + ']')
try {
// 1. Age rating:找编辑中的 appInfo
const ai = await get(
'/apps/' + app.id + '/appInfos?include=ageRatingDeclaration',
)
const editable = (ai.data || []).find((i) =>
INFLIGHT.has(i.attributes.state),
)
const rel = editable && editable.relationships.ageRatingDeclaration.data
const ard = rel && (ai.included || []).find((i) => i.id === rel.id)
const sm = ard && ard.attributes.socialMedia
const smr = ard && ard.attributes.socialMediaAgeRestricted
console.log(
' Social Media: ' +
(sm == null ? '未填' : sm ? 'YES' : 'NO') +
' | Disabled <13: ' +
(smr == null ? '未填' : smr ? 'YES' : 'NO'),
)

// 2. Contact:找 inflight 版本
const vs = await get('/apps/' + app.id + '/appStoreVersions?limit=10')
const inflight = (vs.data || []).filter((v) =>
INFLIGHT.has(
v.attributes.appVersionState || v.attributes.appStoreState,
),
)
if (inflight.length === 0) {
console.log(' Contact: 无 inflight 版本')
} else {
for (const v of inflight) {
const rd = await get(
'/appStoreVersions/' + v.id + '/appStoreReviewDetail',
)
const a = rd.data && rd.data.attributes
const ok = a && a.contactFirstName && a.contactEmail
console.log(
' Contact [' +
v.attributes.platform +
' ' +
v.attributes.versionString +
']: ' +
(ok ? '已填' : '未填'),
)
}
}
} catch (e) {
console.log(' 出错: ' + e.message)
}
console.log('')
}
})()

在这个过程中遇到了几个问题,例如必须有新版本才能修改 App Information 和 Contact Information,所以我不得不为每一个需要修改的 app 都创建了一个 patch 版本,尽管并未提交构建版本和发布,它应该在下次 CI 发布时自动带上避免出现同样的错误。

Snipaste\_2026-08-21\_18-51-22.png

总结

如果你也维护着很多 app,那么也可以未雨绸缪,将所有的必填信息都先填好,避免下次发布时再为此困扰。我也整理出来了脚本 [5] 便于使用,你可以让你的 agent 读取它并使用同样的方式去批量修复这个问题。