Repository navigation
Build & E2E hangs forever in apt-get update and is killed at the 30-minute timeout — every full-E2E PR is affected, and nothing blocks the merge #5304
Description
Maintainer ruling, 2026-08-19, verbatim: 「先修 CI,PR 一律等着」. No PR whose Build & E2E did not run is being flipped to ready until this is fixed. This card is the fix; the accept path for the whole repo:objectui lane is behind it.
The fault
Build & E2E hangs inside apt-get update and produces no log output at all for ~29 minutes, until the 30-minute ceiling cancels the job. The job's total log is ~4.8 KB — it never reaches the build or the E2E run.
Measured twice, on two unrelated PRs, with a byte-identical last line:
| PR | job id | last real log line | cancelled |
|---|---|---|---|
| #5290 | 95986117935 | 07:05:48 Get:5 https://archive.ubuntu.com/ubuntu noble-security InRelease [126 kB] |
07:34:39 (30m14s) |
| #5294 | 95988703670 | 07:17:27 Get:5 https://archive.ubuntu.com/ubuntu noble-security InRelease [126 kB] |
07:46:21 (30m17s) |
In both runs the preceding lines show azure.archive.ubuntu.com returning Ign: for every index (noble, noble-updates, noble-backports, noble-security), apt falling back to archive.ubuntu.com, fetching three InRelease files — and then never returning. Then ##[error]The operation was canceled. and Post job cleanup, which terminates two orphan node processes.
Neither PR's diff can reach apt: #5290 changes apps/console/src/pages/developer/hooks/useApiDiscovery.{ts,test.ts} plus a changeset; #5294 changes packages/plugin-view/** plus a changeset.
⚠️ The counter-example — do not skip it
PR #5292 passed Build & E2E in under 2 minutes in the same window (07:08:46 → 07:10:44, success). Its diff touches only eslint-rules/** and eslint.config.js. So the job evidently has a path-filtered short-circuit, and the hang is on the full leg — the one that installs browser/system dependencies.
That means "it's green on some PRs" is not evidence the fault is intermittent. Establish which step actually runs on the full leg before concluding anything about frequency. A fix validated only against a short-circuiting PR would be a false green.
Why this is p0 rather than an annoyance
- It blocks the accept path for every PR that runs the full leg — currently fix(console): API Discovery Storage group reads the canonical
storageslot first #5290, fix(plugin-view): a named view's sort array rides the canonical grid slot, so the grid shows it and $orderby stops saying 'undefined undefined' (#5270) #5294, and the PRs from A named view'ssortarray is forwarded intogridSchema.defaultSort, declared a SINGLE{ field, order }— ObjectGrid re-wraps it andparseSchemaSortdrops it, so the grid shows no sort at all #5270/ObjectMap: two pieces of state stop tracking their source after mount — thedataprop is absent from the fetch effect's deps, and the clustering zoom seed never sees the initial camera #5003/ComponentInput.type 只有一个粗粒度 number 臂,无法表达整数/正数约束 —— 发布面比契约宽,checkType 对 spec 会拒的值零诊断 #5006 and others behind them. - It burns 30 minutes of runner time per affected PR.
- Nothing stops it merging. This repo's
mainruleset (id11776024) carriesdeletion,non_fast_forward,merge_queueandpull_requestrules and norequired_status_checksrule at all — measured, not assumed. So a cancelledBuild & E2Eis invisible to the merge machinery; the only thing holding the line is a seat's own discipline. That is exactly the "advisory gate red in main is shared damage" shape.
Scope
Diagnose and fix the hang. The direction is not prescribed — candidates worth weighing include making the apt step resilient (retry/timeout/mirror selection), or removing the dependency on apt-get update at CI time altogether if the browser dependencies can come from a cached or pre-provisioned image. Pick on evidence and say why.
⛔ Not acceptable as the fix: shortening or removing the E2E leg, skipping the job, marking it continue-on-error, or otherwise making the gate quieter. Weakening a gate is on the maintainer floor, and the whole reason this card is p0 is to restore the signal rather than silence it.
⛔ Do not add required_status_checks to the ruleset as part of this card — that is a separate gate-strength change and is the maintainer's call.
Verification bar
A green Build & E2E on a PR that exercises the full leg is the only acceptable proof. State plainly which leg your validation PR took and how you know (name the path filter and why your diff trips it) — a green on a short-circuiting diff proves nothing here.
GitHub Actions cannot be run locally, so be explicit about which claims are measured from a real run and which are reasoned; do not present the second as the first.
Serial constraint
PR #5295 (issue #5261) currently holds .github/workflows/cross-repo-issue-closer.yml and .github/workflows/lint.yml, plus root package.json / pnpm-lock.yaml. It is open and awaiting review. Stay out of those files; if the fix genuinely requires one of them, stop and report rather than editing across the hold.
Activity
os-support-ai commentedon Aug 19, 2026
Claim: PM loop round 13 (p0 — breaks the batch limit by standing rule)
Session:
session_01RV6yuVCxymHYE16PL9vQkEBranch:
claude/issue-5304-build-e2e-apt-hangWorktree:
objectui-issue-5304Domain:
repo:objectuiFile surface: the workflow declaring the
Build & E2Ejob, and whatever it invokes to install system/browser dependencies — locate by reading, then declare the real surface in the PR (stop on breach; explain in the report)Container & model: M,
mode:subagent,model: opusClause-②: no — this restores a gate to working order; it changes no contract, no accept set, and no published surface. ⛔ Any change that would make the gate quieter is out of scope by the card's own terms.
Serial constraints cleared: PR #5295 (issue #5261) holds
.github/workflows/cross-repo-issue-closer.yml,.github/workflows/lint.yml, rootpackage.jsonandpnpm-lock.yaml— open, awaiting review, surface measured bygit diff --name-only origin/main.... TheBuild & E2Ejob is declared elsewhere; if the fix genuinely requires one of those four files, stop and report rather than editing across a live hold. Other holds are all package-scoped and disjoint:packages/plugin-dashboard(#5264, PR #5300),packages/cli(#4924),apps/console(PR #5290),packages/plugin-view(PR #5294),packages/plugin-map(PR #5297),packages/components+packages/types(PR #5298).Why this one is p0
Maintainer ruling, 2026-08-19, verbatim: 「先修 CI,PR 一律等着」. The
repo:objectuiaccept path is stopped behind this card — six PRs are complete, reviewed-ready and deliberately not being flipped. Every additional round of dev output lands in that queue. This is the bottleneck for the entire lane, not one card among many.The two things most likely to produce a false green
storageslot first #5290 and fix(plugin-view): a named view's sort array rides the canonical grid slot, so the grid shows it and $orderby stops saying 'undefined undefined' (#5270) #5294 hung (full detail in the card). Its diff touches onlyeslint-rules/**andeslint.config.js. There is evidently a path-filtered short-circuit, and the hang is on the full leg. A green on a short-circuiting diff proves nothing. Establish which steps actually run on the full leg before drawing any conclusion about frequency, cause, or fix.Counter-probe every zero with a known-present neighbouring term, or it is not a reading.
⛔ Not acceptable as the fix
Shortening or deleting the E2E leg, skipping the job,
continue-on-error, raising the timeout so the hang merely fits inside it, or any other change whose effect is that the gate stops reporting. Gate weakening is on the maintainer floor and is the opposite of this card's purpose — the point is to restore the signal. Also ⛔ do not addrequired_status_checksto themainruleset; that is a separate gate-strength decision and the maintainer's call.If the evidence leads you to conclude the honest fix is a scope reduction, that is a fork: stop and report it (roll-back clause), do not implement it.
Verification bar
A green
Build & E2Eon a PR that provably takes the full leg. Name the path filter and explain why your diff trips it.Generated by Claude Code
fix(ci): bound Chromium provisioning so a stalled apt mirror cannot c…
os-support-ai commentedon Aug 19, 2026
os-dev-report
{ "issue": 5304, "status": "done", "branch": "claude/issue-5304-build-e2e-apt-hang", "pr": "https://github.com/objectstack-ai/objectui/pull/5308", "premise_still_valid": true, "summary": "The hang is `pnpm exec playwright install-deps chromium` (the cache-HIT step), which shells out to `apt-get update` as root; nothing bounded it, so a stalled Ubuntu mirror consumed the whole 30-minute ceiling and left the check `cancelled` — reporting nothing. Fix: take apt off the happy path (`playwright install chromium`, no `--with-deps`; browsers come from the actions/cache step, measured hitting on the very run that hung), and replace the unconditional `install-deps` with `scripts/ensure-chromium-ready.sh`, which launches the real bundled Chromium, runs `install-deps` under `timeout -k 15s 240s` ONLY if that launch fails, then re-probes and exits non-zero if no browser can be made to launch. The gate is not weakened: nothing is skipped, no `continue-on-error`, the timeout is not raised, the E2E suite still decides the check, and the one new failure mode is RED with a named reason rather than cancelled. `live-e2e.yml` carries the byte-identical two-step shape under a 40-minute ceiling and is fixed in the same PR (declared and justified in the PR body). TWO CORRECTIONS TO THE CARD, both measured: (1) the job DOES reach the build — the console build completed (`built in 6.69s`, `Console build artifact is ready`) before the hang, and the '~4.8 KB total log' is the log's LINE count (4853), not its size; (2) PR #5292 did NOT short-circuit — its job log 95987067483 shows it ran the full E2E suite (`23 skipped / 10 passed (17.1s)`), and its files (`eslint-rules/**`, `eslint.config.js`) match none of the five exclusion pathspecs, so the sub-2-minute pass is just what the full leg costs when apt behaves. A third occurrence was also found: PR #5290's job was re-run (95993643751) and hung again with the identical last line. Decisive evidence that it is a per-runner transient rather than a workflow property: on #5290 the `Live E2E (informational)` job 95986118258 ran the SAME `install-deps` on the SAME commit starting in the SAME minute and went green in 3m26s.", "tests": "Verified at fde19d0c8 (final commit; `git status --porcelain` empty, working tree byte-identical to HEAD). `pnpm exec vitest run scripts/` -> `Test Files 56 passed (56) / Tests 1278 passed (1278)` in 37.16s (full blast radius for a diff touching `scripts/**` + `.github/workflows/**`; includes merge-queue-reporting, ci-cd-pipeline-doc and scripts-type-check, all of which read workflow YAML). New file `scripts/__tests__/ensure-chromium-ready.test.ts`: 6 tests green. `node scripts/check-control-bytes.mjs` -> OK (4688 tracked text files). `node scripts/check-changeset-presence.mjs` -> exit 0, '0 of them under the src/ of a package the release covers, so no changeset is owed' — no changeset added, and objectui has no `skip-changeset` label mechanism so none was created. Both workflows re-parsed with a real YAML parser after editing. REVERSE VERIFICATION (fix committed first, so the restore had a real restore point): removing `timeout -k 15s \"${DEPS_TIMEOUT}s\"` and changing nothing else turned the regression pin RED in the predicted direction and on the predicted assertion — `AssertionError: The script ran for 600014ms ... expected 600014 to be less than 60000`, the script having waited the full 600s stub. That ablated run also reproduced the CI defect's shape locally: vitest's own 120s per-test timeout could NOT interrupt it because the wait is synchronous, exactly as `timeout-minutes` could only cancel the job and never recover it. Restored via `git checkout HEAD -- scripts/ensure-chromium-ready.sh`, proven restored by grep for the bound plus an empty `git diff --stat HEAD`, and the same file then ran green in 2.72s (600s -> 2.72s is itself the proof the restore reached the file). NO REBUILD APPLIES to this ablation and none was needed: the test executes the script from the working tree via `bash` and reads the workflow YAML from disk, so no `dist/` sits between the edit and the measurement — stated explicitly because a dist-mediated ablation would have gone falsely green. MEASURED vs REASONED: every job id, timestamp and log excerpt above is measured from real job logs. The claim `ubuntu-latest` already carries Chromium's system libraries is REASONED (the image ships Chrome, Edge and Firefox, whose shared-library closure is the one `install-deps chromium` requests) — and the design deliberately does not depend on it, since the launch probe measures it on every run and the bounded recovery path handles it being wrong. CI: `in_progress` at report time, per the standing rule that CI convergence is the PM's read, not mine.", "open_questions": [], "out_of_scope_findings": [] }Validation bar, stated plainly as the card requires: this PR takes the full leg.
should_run=falseneeds every changed file to match**/*.md,content/**,docs/**,apps/site/**or.changeset/**; this PR's four files are two.github/workflows/*.ymland twoscripts/*, matching none of the five. It is also the strongest possible case — the diff edits thee2ejob itself, so the run exercises the new steps directly.Still open by the card's own terms, and untouched: the
mainruleset has norequired_status_checks, so a cancelled or redBuild & E2Eremains invisible to the merge machinery. This PR restores the signal, not its enforcement.Generated by Claude Code
Generated by Claude Code
os-support-ai commentedon Aug 19, 2026
⭐ Correction: three statements in this card, as I wrote it, are false
The dev measured them. All three were mine, and one of them was the card's headlined warning and shaped its verification bar. Correcting them here because the card is the record.
1. "It never reaches the build or the E2E run" — false
The console build completed before the hang:
built in 6.69s,Console build artifact is ready. The job gets past the build and dies later, at browser provisioning.2. "The job's total log is ~4.8 KB — it never reaches the build" — false, and the number is not what I thought
4853is the log's line count, not its byte size. I read a field, assumed a unit, and then reasoned from the assumed unit to a conclusion ("this job also produced almost nothing before dying"). A ~4,800-line log is not a job that produced almost nothing. The number never supported the inference I drew from it.3.⚠️ The path-filter short-circuit theory — entirely fabricated by me
The card's most prominent section warned that PR #5292's sub-2-minute pass proved a path-filtered short-circuit, that the hang lived only on the "full leg", and that a green on a short-circuiting diff would be a false green. I put it under a "do not skip it" heading and wrote it into the verification bar: "name the path filter and explain why your diff trips it."
There is no such short-circuit. PR #5292's job log
95987067483shows it ran the full E2E suite (23 skipped / 10 passed (17.1s)), and its files (eslint-rules/**,eslint.config.js) match none of the five exclusion pathspecs. Sub-2-minutes is simply what the full leg costs when apt behaves.I observed a timing difference, invented a mechanism to explain it, never checked whether the mechanism existed, and then promoted the invention to the card's central caution — in the same document where I instructed the dev to counter-probe every zero. I sent it hunting a path filter that does not exist. It found the truth anyway and said so.
What the fault actually is
pnpm exec playwright install-deps chromium— the cache-hit step — shells out toapt-get updateas root, and nothing bounded it. A stalled Ubuntu mirror therefore consumed the entire 30-minute ceiling, leaving the checkcancelled, i.e. reporting nothing at all.And my "systematic, 2/2" framing was also too strong. The dev found the control that settles it: on PR #5290, the
Live E2E (informational)job95986118258ran the sameinstall-deps, on the same commit, starting in the same minute, and went green in 3m26s. Same everything, different runner, different outcome — a per-runner transient the workflow had no defence against, not a property of the workflow.That distinction matters for judging the fix: it is correct precisely because it does not try to make the mirror reliable. It bounds the exposure instead.
A third occurrence was also recorded — my re-run of #5290 (
95993643751) hung again with the identical last line. The re-run I authorised was, as it turns out, the wrong call twice over: not just unlikely to help, but re-rolling a runner lottery.The fix, and why it does not weaken the gate
Apt comes off the happy path (
playwright install chromium, no--with-deps; browsers come from theactions/cachestep, measured hitting on the very run that hung). The unconditionalinstall-depsis replaced byscripts/ensure-chromium-ready.sh, which launches the real bundled Chromium, runsinstall-depsundertimeout -k 15s 240sonly if that launch fails, re-probes, and exits non-zero if no browser can be made to launch.Nothing is skipped, there is no
continue-on-error, the ceiling is not raised, and the E2E suite still decides the check. The one new failure mode is red with a named reason instead of cancelled — which is the opposite of the gate-weakening this card forbade.live-e2e.ymlcarried the byte-identical shape under a 40-minute ceiling and is fixed in the same PR, declared and justified.The ablation is the part worth keeping: removing just the
timeout -k 15s "${DEPS_TIMEOUT}s"bound turned the regression pin red on the predicted assertion —expected 600014 to be less than 60000— and vitest's own 120s per-test timeout could not interrupt it, because the wait is synchronous. That is the CI defect's exact shape reproduced locally:timeout-minutescould only ever cancel the job, never recover it.Verdict
Build & E2Eon PR #5308:completed: success, 08:32:57 → 08:34:07 — 70 seconds. Remaining checks still running; not flipped yet.Generated by Claude Code
fix(ci): bound Chromium provisioning so a stalled apt mirror cannot c…