Skip to content

[PR] Blog refresh falls back to the GitHub contents API when raw.githubusercontent.com is unreachable - #5394

Merged
ranxianglei merged 6 commits into
masterfrom
2026-11-01_blog-fetch-fallbacks
Nov 1, 2026
Merged

ranxianglei merged 6 commits into
masterfrom
2026-11-01_blog-fetch-fallbacks

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Why

From mainland-China networks (and other GFW-class egress splits), `raw.githubusercontent.com` is routinely blocked while `api.github.com` stays reachable. The blog puller only knew the raw host, so EVERY hourly round failed there and those instances were stuck on the npm-bundled copy forever — new posts never arrived no matter how long you waited.

What changed (host-side only)

Each file body now walks a two-member fallback chain, both first-party GitHub hosts only:

  1. `raw.githubusercontent.com` (byte-identical to before)
  2. `api.github.com/...` repo contents endpoint — its JSON answer carries the body base64-inline; GitHub omits bodies >1 MiB there, which cannot bite because every legal text document is capped at 512 KiB.

No third-party mirror joins the chain (owner decision 2026-11-01: an independent CDN copy of the tree is not an acceptable content source).

Behavior keeps:

  • per-round sticky walk start (once a host proved dead, later files skip it instead of re-paying a 5 s timeout each);
  • ok log line records which members served: `(via raw)` / `(via github-api)`;
  • both hosts dead → existing fail-soft: last good overlay kept, ``offline`` marker on the feed, nothing throws;
  • true 404s / >1 MiB omissions still drain the chain to clean misses (image reference dropped, round succeeds).

Tests

`tests/web-blog-refresh.test.ts`: chain facets rewritten — API-serves-when-raw-is-dead (bilingual pair intact over the JSON path), both-first-party-dead fails soft, healthy-raw rounds never touch any non-GitHub host (now trivially: there are only two hosts), >1 MiB image omission drains to a dropped reference. 25/25 green plus neighbors (web-blogfeed, web-blog-route) green; tsc clean.

@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-11-01_blog-fetch-fallbacks (3dd937b)

Latest build — install this exact version

npm install -g billion-context@0.0.7756-pr.5394.e703aca

This exact version is immutable and reproducible. The rolling pr tag tracks the newest test build across all PRs.

Recent builds (commit → version)

commit version build
3dd937b billion-context@0.0.7756-pr.5394.e703aca build
40b8429 billion-context@0.0.7667-pr.5394.82877ac build
6882e4d billion-context@0.0.7639-pr.5394.436c82b build
6d16e17 billion-context@0.0.7631-pr.5394.989b51c build
14a1f46 billion-context@0.0.7630-pr.5394.3059c73 build
4d9d4a2 billion-context@0.0.7627-pr.5394.7f3a599 build

Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr5394.tgz
npm install -g package

Install instructions are refreshed on each push.

@ranxianglei ranxianglei changed the title Blog refresh falls back to the GitHub contents API when raw.githubusercontent.com is unreachable [PR] Blog refresh falls back to the GitHub contents API when raw.githubusercontent.com is unreachable Nov 1, 2026
ranxianglei pushed a commit that referenced this pull request Nov 1, 2026
ranxianglei pushed a commit that referenced this pull request Nov 1, 2026
@ranxianglei ranxianglei added bug Something isn't working p2 scope/ui labels Nov 1, 2026
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b)

查重: 不重复。 按失败签名与主题检索了 open+closed 的 issue/PR(blog refresh、raw 主机回退、GFW 出口):相关条目全是上游血缘或不同机制——#4906(master 自动拉新文的原始需求,已关)、#4917/#4915(实现 PR)、#5171(两套冲突实现的裁决,已关)、#4958/#4970(博客资产 SVG 沙箱,不同机制)、#5070(articles.json bundle 条目,不同机制)。没有任何条目处理「raw 不可达 → 整轮失败」这个问题。

标签: bug / scope/ui / p2(+平台 pr 标)——面板博客更新通道缺陷,web 面,局部功能异常但主功能可用。

审的是 GitHub PR #5394(分支 2026-11-01_blog-fetch-fallbacks)。diff 全量通读(2 文件 +253/−29),对照 master 既有行为逐项过,本地跑 tests/web-blog-refresh.test.ts + web-blogfeed + web-blog-route 共 42/42 绿(先补建了 kernel dist,否则是环境性模块缺失,与本 PR 无关)。

结构化八问

  1. 是什么问题: 大陆等 GFW 类出口下 raw.githubusercontent.com 被墙而 api.github.com 可达,面板博客每小时的 GitHub-master 刷新在这种网络下每轮必败——实例永远停在 npm 包内置快照,新文章永远到不了。
  2. 什么原因导致的: 文件体抓取路径只有单一来源(raw 主机),无回退成员——网络层不可达直接把整轮打挂,fail-soft 只负责保留旧快照,不负责换路。修的是产生失败的那个环节(取数路径),不是症状。
  3. 影响范围与严重程度: 只影响面板博客的更新能力;面 = raw 不可达出口的实例;程度 = 个别功能异常(博客不更新,其余全部可用)→ p2。海外路径行为零变化(有负向测试钉住)。
  4. 怎么修复的: 每个文件体走两成员 first-party 回退链:raw → GitHub contents API(JSON 内 base64 内联正文);per-round sticky walk start(一个主机被证明死了,同轮后续文件直接跳过它,不再每个文件重付一次 5s 超时);ok 日志记录哪些成员实际服务了;双死仍走原 fail-soft(留最后好快照 + offline 标记,不抛错)。第三方镜像明确不入链(owner 决策)。
  5. 是否有回归问题: 逐项过,无意回归未发现。健康海外路径字节级不变(新增负向测试钉住:正常轮不碰白名单外的 API 文件端点);fail-soft/offline 标记语义保持(both-dead 测试断言 ok:true + feed.error="offline");四个预算全保留(text 512 KiB / image 2 MiB / 整轮 8 MiB / 200 文件),API 路径另加 base64 预解码门(~4/3 膨胀)+ 解码后精确检查;真 404 与 >1 MiB 省略把链排空到干净 miss、引用被丢弃、整轮成功;图片严格性保持(所有源硬失败且无干净 miss → 仍像旧行为一样让整轮失败)。有意变更均已披露:① raw 死的网络现在经 API 成功(修复本身);② ok 日志行追加 (via raw|github-api|raw+github-api) 后缀(纯增量);③ 不存在的文件(可选 manifest、缺失图片引用)现在会多花一次 API 调用走完 drain(有界)。残余约束(非回归,仅记录):raw 死网络上每轮文件数计入未认证 API 配额(60/h/IP)——当前约 10 次/轮,离上限很远;将来超了也是优雅降级成 offline 标记,不坏数据。
  6. 是否破坏缓存: 不涉及(不碰会话/前缀缓存/持久化;博客 overlay 自身的 last-good 保留逻辑未动)。
  7. 是否改了设计方向: 没有——维持「只读公开 API、无凭据、无配置键」约束,只是给取数路径加了第二个 first-party 来源。
  8. 是否引入新配置: 没有。

测试有效性: 4 个新链测试都有牙——断言真实 feed 状态(标题/中文对/html/资产存在性),用请求台账做负向断言(任何第三方主机零接触;健康轮不碰 API 文件端点),offline 标记,引用丢弃;gate 双向齐全(#3617 纪律)。小缺口(不拦):sticky walk start 本身的行为没有独立断言,只被「整轮成功」间接覆盖。

顺手小修(已推 PR 分支,独立标注 commit):

  • docs(#5394) 14a1f46 —— 分支早期迭代残留的两处 jsDelivr 旧话(the contents API / jsDelivr stand in、PLAIN source (raw/jsDelivr))与代码事实不符(链里只有两个 first-party 成员),改对;
  • refactor(#5394) 6d16e17 —— 逐段 URL 编码表达式在 4 处复制(rawUrl/sourceChainFor/fetchApiText/fetchApiImage),抽成 encRelPath。
    本地验证:refresh 套件 25/25 + 邻居绿。

CI: 审时 head 4d9d4a2 —— 27 success / 11 skipped / 8 个 test shard 在跑,零失败;新 head 6d16e17 上 CI 重跑中。

判定: 可合(等新 head CI 转绿即可合并)。备注一句:此 PR 无独立源 issue(owner 指令直接触发,决策已记录在 PR 正文),按 loop guard 不补占位 issue。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b)

补充一行:新 head 6d16e17d6(含两处顺手小修)的 CI 已全部转绿——30/30 completed,0 failed,0 pending。PR #5394 现在处于可合状态,合并动作留给你。

ranxianglei pushed a commit that referenced this pull request Nov 1, 2026
…n the two header links (they rendered glued), correct the convention reference (#5393 is an unrelated kernel issue; the blog:<id> convention is stated in #5394), and pin the new anchor in web-blog-route
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b)

Review 结论:APPROVE(可合)——独立复审,最终 head 6882e4d35(含我补的两个小修 commit)。本轮不沿用上一轮审核的表述,证据链全部重做。

我独立验证了什么

  • Diff 范围:git diff 当前 master(7578e8184)..head 恰好 2 个文件(+258/−30):src/web/blog-refresh.ts、tests/web-blog-refresh.test.ts;分支直接坐在 master tip 上,无无关改动、无 lockfile/生成物。
  • PR head 全文件 507 行逐行通读:回退链逻辑、预算上限、fail-soft、并发语义、安全面(无凭据、host 钉死两个一等域名、UA 头不变)。
  • 本地定向验证(非全量):kernel dist 构建后 tests/web-blog-refresh.test.ts + web-blogfeed.test.ts + web-blog-route.test.ts 43/43 通过;tsc --noEmit 干净。
  • CI:旧 head 6d16e17d6 上唯一完成的 CI run 是 skip-scoped run(test/static-gates/mysql-contract/red-signal 全部 skipped,只跑了 12 个轻量 guard)——即该 head 上核心矩阵从未真正跑绿,上一轮「30/30 全绿」的读数基于这个 skip run。我推小修后新 head 6882e4d35 触发了完整核心矩阵(11 test legs + static gates)+ 全部 e2e lanes:0 失败,全绿。

我顺手补的两个小修(独立标注 commit,已推 PR 分支)

  1. docs(#5394) 7d525a7:contentLengthOf 的文档注释仍引用改名前的 getText/getImage,与代码事实不符,改为现名 fetchPlainText/fetchPlainImage。
  2. test(#5394) 6882e4d3:补 sticky walk start 断言(上一轮点名的缺口)——raw 死掉时 8 个文件的 raw 重试次数 ≤ CONCURRENCY(4);无 stickiness 时会是 9 次。该界由 mapPool 波次语义决定,注释里写清了为什么是 4。

结构化八问

  1. 是什么问题:出口只通 api.github.com、不通 raw.githubusercontent.com 的网络(大陆常见拓扑)上,blog 每小时的拉取轮次整体失败,用户永远停留在 npm 打包时的博客快照,新文章永远看不到。
  2. 什么原因导致的:文件正文的唯一来源钉死在 raw host——「两个 GitHub 域对任意网络不等价可达」是网络拓扑事实,不是代码缺陷;正解是加同源备援源,而不是对单一源加重试。
  3. 影响范围与严重程度:受影响面 = 该类出口的所有实例(blog 面板内容新鲜度);严重度为体验降级——内容始终可用(fail-soft 保留 last-good + offline 标记),只是不再更新;模型流量/压缩主路径零影响。
  4. 怎么修复的:每个文件正文走两级 first-party-only 回退链(raw 优先 → contents API base64 内联正文),轮内 walk 起点粘性前进(某源被证明死后后续文件直接跳过,不再逐个付超时),双源皆死回落到原有 fail-soft。
  5. 是否有回归问题:既有行为逐项清点(见下),未发现无意回归;全部变化均为有意且已披露。
    • 健康网络路径:URL 构造字节级一致(encRelPath 抽取是纯重构)、raw 优先序不变 ✓
    • 预算上限(512KiB 文本/2MiB 图/8MiB 轮总/200 文件/50 图候选)、TTL、单飞合并、小时自调度链:未动 ✓
    • 原子注册或 offline 标记(half-fetched tree never render):未动 ✓
    • 图片 404/oversize 静默 miss 语义:保留(fetchPlainImage 逻辑原样)✓
    • fail-fast 严格性:文本路径仅当两源皆败才失败整轮;图片路径在「有硬错且无任何 clean miss」时仍 rethrow ✓
    • settleRoundImmune 的 unhandledRejection 防护、warm 触发、disabled 逃生口:未动 ✓
  6. 是否破坏缓存:不涉及(无缓存/重试游标/去重键;本模块自身无持久化)。
  7. 是否改了设计方向:不涉及(单模块传输层修复;架构/边界/数据流不变;kernel/ 未触碰,无 kernel-change 声明,不触发授权门禁)。
  8. 是否引入了新的配置:未引入(无新 env/config key/flag,符合本模块「无凭据、无配置」约束)。

行为变更披露(旧值 → 新值)

  1. raw 不可达网络:每轮失败 → 轮次成功(修复本身)。
  2. 不存在文件(missing manifest / 404 图片引用,所有网络):旧 = 1 次 raw 调用即 miss;新 = raw + 1 次 contents-API drain 调用才 miss。有界(≤51 次/轮,常态 0–2 次),消耗少量匿名 API 配额(60/h/IP),当前博客规模下可忽略。
  3. raw 不可达网络上的 1–2MiB 大图:引用被丢弃、其余轮次成功(contents API 省略 >1MiB 正文 + first-party-only 决定的必然结果,PR body 已披露)。
  4. ok 日志追加 (via raw|github-api|…);offline 原因措辞 raw GET → plain GET(仅日志外观)。
  5. 病态边界(观察项,不拦):文本文件恰为 524287–524288 字节(512KiB 上限顶格 2 字节窗)时,API 路径预解码估算 ceil(L·3/4) 会假拒,raw 不可达网络上该轮 fail-soft(下小时自愈;解码后精确检查仍是权威;保守方向是「拒而非收」,不改)。
  6. 轻微代码气味(观察项,不拦):sourceChainFor 第二成员的 url 字段构造后未被读取(fetchApiText/fetchApiImage 各自重建同一 URL)——属统一链结构,可接受。

测试有效性:4 个新测试有牙(断言 feed items/html 内容/资产存在性/offline 标记/host 白名单,非恒真断言),能抓住原始 bug 类(raw 死 → 轮次失败)及 base64 解码、双语装配、fail-soft、第三方 host 泄漏、>1MiB omission 的回归;sticky-start 缺口已补。残余小缺口(图片双源硬错 → 轮次失败的路径无直接 pin,其文本类比已被钉住)不阻塞。

diff 整洁度 / 代码整洁:每一行 diff 都与本 PR 目的相关;命名达意、无死代码(除上述观察项)、无魔法数字、注释讲为什么、风格与仓库一致、零新依赖。通过。

判定:可合。 建议以 head 6882e4d35 合入(owner 手动 merge)。

[system] Review lane 完成:PR #5394(镜像 #5396)独立复审完毕——APPROVE;补 2 个小修 commit(docs 7d525a7 / test 6882e4d);新 head 6882e4d 核心矩阵全绿(旧 head 的「全绿」实为 skip-scoped run,已在文中披露);等待 owner 合并。

ranxianglei-devbox and others added 6 commits November 1, 2026 23:49
articles.json entries may carry a positive-integer discussion number (written back by the blog pipeline workflow when it creates the article's GitHub Discussion thread). The remote feed item now carries it as item.discussion — a plain number, dropped by JSON serialization whenever unrecorded, so clients can show the comment button only with an exact recorded link. Posts that never go through the manifest never get the field.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working p2 scope/ui

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant