B站Toy同步事故复盘:版本指纹先定性,外部cron做兜底
一句话:分发平台的线上产物自带版本指纹(URL 后缀
-v1、外壳 meta),先抓线上实际版本定性”管道坏了”还是”从未走通”,再回头拆管道;调度、分发、更新三环,各自都需要一条 GitHub 之外的兜底。
事实记录(不可修改区)
- 项目:风眼 · Typhoon Eye(台风实况 + 分级应急预案,零依赖静态单页;仓库
Mr-Salticidae/typhoon-eye),发布为 B 站 Toy(id8980525008896,slugYliUzbE5TOqySu4G),B站 AI 创造公开赛参赛作品,获官方推流 - 症状(2026-07-11 晚):“Toy 平台无法同步更新”——线上仍是 v0.2 首版,包内数据停在 07-10 12:25;而仓库数据管道(Actions 每 30 分钟抓浙江台风网 → Pages)全程正常
- 诊断结论:三环各自独立断裂——① CI 静态校验
toy_doctor.py把 index.html 的href="./"解析成资产.判 missing ERROR,preview 工作流两次全挂;②toy.yaml缺项目id,且仓库根本没有任何会调用 update 的链路;③ 线上外壳 iframe 指向…-v1——首版之后从未成功更新过 - 修复与加固时间线:07-11 夜修 doctor 误报 + 补 id,经 B 站发布平台网页端提交更新(v0.3 = v2 过审);07-12 晨 v0.4 = v3 上线(四源镜像取数 / 15 分钟班次 / jsDelivr 主动 purge / tel 拨号三级唤起);07-12 07:52 外部 cron-job.org 兜底首次执行即真实补救一次 GitHub 丢班(数据已过期 37 分钟,watchdog 补抓提交)
- GitHub 调度丢班实录:07-11 18:00–23:00 UTC,每小时 4 班定时任务仅被放行约 1 班;22:03 UTC 后连丢 4 班,数据滞后 69 分钟;GitHub 状态页全程显示 operational
- 数据来源:GitHub Actions API 运行记录、线上外壳页与 bebox.net 内容抓取比对、git log、cron-job.org 执行记录
排障过程
- 拉 Actions 运行记录:数据管道(抓取/Pages)全绿,唯独 toy-preview 两次失败,全部死在 Validate 步——问题不在数据侧。
- 复现校验器判定(本机无 Python,直接读
check_ref源码走查):href="./"被按相对资产拼成.,不在文件集合 → ERROR。index.html 全文唯一 ERROR 即此误报,改href="index.html"一行过检。 - 抓线上外壳页:B 站 Toy URL 只是壳,真实内容在沙箱 iframe(
bebox.net/toy/<slug>/<id>-v1/)。-v1一锤定音”首版后从未更新”;且壳里window.__TOY_META__直接暴露了 update 流程缺失的toy_id。 - 走网页端更新:CLI update 需要 cookie secret(未配置且不宜经手);发布平台网页端在已登录会话下走”发布记录 → 更新”,slug/标题/可见性全保留,不会误建新 Toy。
- 修完复发再排查:次日用户仍见旧数据——Actions 记录证明是 GitHub schedule 整点丢班(非应用问题)→ 上外部 cron 兜底,首跑即真实补救。
结果分析
- 符合预期:三环全部修复,v3 线上代码验证含全部加固;外部兜底与 GitHub 自身班次形成三路触发,任一路活着数据滞后不超过约 20–35 分钟;四源镜像实测全 200 +
CORS=*,数据更新后主动 purge,jsDelivr 1 分钟内跟上 - 未验证部分(诚实边界):① tel 拨号三级唤起只验证了代码部署,B站 App 真机唤起成功率未实测,当前保底是”号码已复制”提示;② jsDelivr 大陆可达性是常识判断 + 本机验证,未从墙内多地实测;③ cron-job.org 免费档长期稳定性待观察;④ 15 分钟页内自动刷新未在真实用户端观测;⑤ PAT 90 天有效期(2026-10-10 到期),轮换是人肉环节
方法论沉淀
⭐⭐ GitHub schedule 必须配 GitHub 之外的触发兜底(二次验证)
核心:GitHub Actions 的 cron 是尽力而为,高峰期丢班/长时间不触发无 SLA,且状态页不反映——不能作为生产级定时任务的唯一触发源。
来源:两个仓库、两种表现——快讯 workflow 的 schedule 自上线起零自然触发(见 云端定时内容生产连环坑复盘_全绿不等于已发_v1 坑①);本次 typhoon-eye 每小时 4 班仅放行约 1 班、连丢 4 班致数据滞后 69 分钟。
验证状态:二次验证 ⭐⭐(跨仓库、跨月份两例)
操作规则:
- 外部定时源(cron-job.org / Cloudflare Worker Cron)按业务周期
workflow_dispatch兜底; - 外部触发的目标选”过期才动手”的 watchdog 任务而非主任务——幂等,GitHub 自身正常时空转零成本,不产生重复提交;
- PAT 用细粒度、单仓库、仅
actions: write; - 兜底线路自身要可观测:开外部服务的失败邮件告警。
反例/边界:分钟级实时性要求不适用(此方案最坏仍有 20–35 分钟滞后),应改常驻服务;外部服务与 GitHub 同时故障无解,属可接受残余风险。
⚠️ 版本指纹先定性,再拆管道(首次发现)
核心:分发平台的线上产物往往自带版本指纹(URL 版本后缀、外壳页 meta、页脚版本号)。排”不更新”类故障,第一步抓线上实际版本——一步区分”管道坏了”和”从未走通过”,避免在正常环节空转;外壳页还可能直接暴露你缺失的元数据(本次 __TOY_META__ 给出 update 必需的 toy_id)。
来源:iframe -v1 后缀直接证明首版后从未更新;三环断裂里两环由此反推锁定。
验证状态:首次发现 ⚠️
⚠️ 校验器 ERROR 先复现其解析逻辑(首次发现)
核心:静态校验器(doctor/linter)报 ERROR ≠ 代码真有缺陷。toy_doctor 把合法的 href="./" 按相对资产解析成 . 判 missing——改代码前先读校验器源码复现判定路径,分清”真缺陷”与”解析盲区”,再选成本最低的一侧改(本次改代码一行,不动校验器)。
来源:preview 工作流两次全挂的唯一 ERROR 即此误报。
验证状态:首次发现 ⚠️
⚠️ 沙箱 iframe 内外部协议链接会静默失效(首次发现,未真机验证)
核心:托管平台把第三方内容装进 sandbox iframe(B站 Toy → bebox.net)时,tel: / mailto: 等外部协议的本框架导航可能被静默吞掉,用户只觉得”点了没反应”。
操作规则:检测 window.self !== window.top 后走三级唤起(top 导航 → window.open → 本框架导航)+ 始终自动复制号码 + toast 提示;顶层环境保持原生行为不动。
反例/边界:B站 App 真机唤起成功率未实测,此条整体仍是待验证假设;保底体验(“号码已复制,可粘贴拨打”)必须存在。
验证状态:首次发现 ⚠️
真机追记(2026-07-12,勘误不覆盖原文):B站 App 真机实测,上面的”三级唤起”第一级(顶层导航)是有害的——App 的 webview 未注册 tel: 处理器,顶层导航把整个 Toy 页跳到 net::ERR_UNKNOWN_URL_SCHEME 全屏错误页,体验比”点了没反应”更糟。修正后的规则:沙箱内禁止一切页面级导航(顶层 / window.open / 本框架都不行),改用隐藏 iframe src = tel:... 触发——支持 tel: 的浏览器静默唤起拨号,不支持的静默失败、页面不动;复制号码 + toast 保底不变。教训:webview 对未注册 scheme 的处置(静默吞 / 全屏报错)因宿主 App 而异,“尝试拨号”的任何路径都必须保证失败时页面无感。
⚠️ 单源静态分发升级为多镜像取最新(首次发现)
核心:*.github.io 在大陆不稳,单源在线数据一失败就静默降级旧快照,用户观感 = “从不更新”。并行请求 Pages + jsDelivr×2 + raw 四源,以数据内时间戳取最新(而非先到先得);镜像有缓存 TTL,数据每次更新后必须主动 purge(实测 purge 后 jsDelivr 1 分钟内跟上)。
来源:v1/v2 时期在线取数逻辑存在但用户仍见旧数据的成因之一。
反例/边界:首个成功源之后留 800ms 收集窗口,是首屏延迟与数据新鲜度的折中;jsDelivr 大陆可达性未多地实测。
验证状态:首次发现 ⚠️
下次改进
- B站 App 真机实测:拨号三级唤起成功率、15 分钟自动刷新是否生效
- 观察 cron-job.org 一周执行记录与 GitHub schedule 自然触发率,回填本档验证等级
- 2026-10 上旬 PAT 轮换(90 天到期),提前建提醒
- 官方推流若带来流量,关注 Pages 带宽配额与 jsDelivr 命中情况
附录:发布平台网页端更新 Toy 的正确姿势(实测 SOP,2026-07-12 回填)
CLI 更新需要 cookie secret,网页端(bilibili.com/toy/publish)是已登录会话下的首选路径。三次实战(v2/v3/v4)踩出的坑与固定动作:
- “更新”按钮有概率把你带进新建表单——实测 3 轮里 2 轮首次点击后落到”上传发布”页:页面地址栏是预生成的随机 slug、标题为空、底部按钮是”发布”。此时上传等于创建第二个 Toy,不是更新。成因疑似 SPA 状态未就绪;窗口较窄时记录行按钮还会从”更新/删除”文字变成 ↻/× 图标,更易误判。
- 进入更新模式的三重判据(缺一不可,建议程序化断言而不是目测):① 顶部出现”正在更新 <原标题>“横幅(带”取消”);② 页面地址锁定为原 slug;③ 底部按钮文案是”更新”而非”发布”。首点失效就回”发布记录”等列表渲染完,悬停目标行重点一次。改良(第 4 轮验证):不用坐标点击,直接在页面 JS 上下文定位记录行的
button.update-btn调.click(),再原子断言 ①②——实测首次即中,绕开了坐标点击对悬停态/渲染时序的依赖。 - 自动化上传的可行注入法:浏览器扩展的文件上传白名单不含任意本地路径;绕法是在页面 JS 上下文里
fetch仓库 raw 地址的 zip(按 commit 固定 URL),构造File+DataTransfer赋给input[type=file](accept=“.zip,.html,.htm” 的那个)并派发change事件。注入前后都校验字节数与预期一致。 - 点”更新”前验预览包:上传成功后页面会生成
toy/preview/preview_xxx/预览地址,先 fetch 预览包内的关键文件(如本次的assets/toy-runtime.js)确认确实是新代码,再提交——防止传错包白走一轮审核。 - 过审判据:外壳 iframe 的版本后缀 +1(
-v3→-v4)即上线;两次实测审核耗时约 10 分钟。更新不改 slug、标题、封面与可见性(全部保留原值)。
关联文档
- ⚠️ 云端定时内容生产连环坑复盘_全绿不等于已发_v1 —— 前档:其坑①(schedule 不触发)由本档提供第二例实证,「schedule 不可靠」升 ⭐⭐ 二次验证;外部兜底方案见本档
- ⚠️ 每日定时任务自动更新内容板块_首次自动化生产落地_v1 —— 同族:定时内容生产三律(自动化只碰数据 / 人先跑通 / 宁缺毋滥)
- ⚠️ Claude预览环境不派发滚动事件_滚动类功能无法行为验证_v1 —— 同族:托管环境(预览 iframe / 沙箱 iframe)行为差异导致的功能失效与验证盲区
- ⚠️ 2026-07-30_风眼九段线问题地图事故复盘_v1 —— 同项目(风眼)另一次事故,这次是内容合规而非管道:自绘南海断续线插图把海南岛与北部湾画到线外侧,活了 19 天;与本档互补的一点是「双发布面下线要各清算一次」——Pages 推送即生效、Toy 要过审才生效
- 对外版:
08_对外分发/B站Toy发布后更新不动了_三环断裂排障复盘_公开版.md(B站 AI 开发者小站,【踩坑记录】)