Skip to content

[ci] a hung Post Turbo Cache save runs the Type Check job into its 20-minute ceiling, turning an all-green merge-queue run into a CANCELLED check that ejects the PR #6577

Description

@os-support-ai

Measured once, on #6571, 2026-08-26. A pull request whose every check was green was ejected from the merge queue with CI_FAILURE — not because anything in its diff failed, but because the actions/cache@v6save step of the type-check job hung until the job hit its timeout-minutes: 20 ceiling.

What happened

Merge group gh-readonly-queue/main/pr-6571-b1a732b22a13eae35bd71711f809d38e22dbf514 contained only #6571 — a single-PR group, so there is no batch-mate to attribute this to. CI run 32974029712 (event: merge_group) ended cancelled, the queue read that as CI_FAILURE, and the PR was dequeued at 13:45:26Z.

Nine of the run's ten jobs are success or skipped. Every substantive check passed:

jobconclusion
Build & E2Esuccess (done 13:25:38Z)
Changeset Fixed Group Checksuccess
Build Docssuccess
Test (shard 1/4 … 4/4)success, all four
Type Checkcancelled

The Type Check job (98194272759) is the whole story, and its own step list is the evidence — all 21 real steps succeeded, the gate itself included:

step 20 Run type-check 13:25:59 -> 13:31:31 success
step 21 Type-check repo-root vitest setup files 13:31:31 -> 13:31:35 success
step 40 Post Turbo Cache 13:31:35 -> 13:44:44 CANCELLED (13m09s)

The job started 13:24:42Z and was cancelled 13:44:44Z — 20m02s, an exact match for the timeout-minutes: 20 declared on the type-check job in ci.yml. It spent 13 minutes 9 seconds inside the post (save) phase of the Turbo Cache step (actions/cache@v6, caching .turbo/cache) and was still there when the ceiling fired.

It is transient, not a property of the workflow

Two independent controls, both against this same code:

  1. The same job passed on the PR head thirteen minutes earlier. On cdf02d2e538b57f88248894610f3d07819067267, Type Check (check run 98190108000) ran 13:11:40Z → 13:17:33Z = 5m53s, success, its own cache post-step included. All 29 check runs on that head are green or skipped; zero failures.
  2. Eight of the nine merge-group CI runs in the surrounding 101 minutes went green. Window 11:56:42Z–13:37:37Z, 30 CI runs: merge_group 8 success / 1 cancelled (this one), pull_request 13 success, push 7 success. The only other non-success in the window is action_required on changeset-release/main, which is the release bot awaiting approval.

#6571's diff is apps/console/src/utils.ts plus a changeset — no workflow, no turbo config, no build-graph change. Nothing in it can reach the cache save.

Why this shape deserves a fix rather than a shrug

This repo has already written the general form of the defect down, in ci.yml itself, in the objectui#5304 comment block about apt-get hanging the E2E job:

apt has no timeout covering a mirror that connects and then trickles, so the only backstop was timeout-minutes: 30 — which converts a transient network fault into a CANCELLED check: a gate that reports nothing at all.

That is exactly this, with the actions/cache upload standing in for apt — plus one twist that makes it worse: the gate had already returned its verdict.Run type-check reported success at 13:31:31Z. Everything after that point was cache bookkeeping. A transient upload stall then discarded a verdict that was already recorded, and the merge queue — which cannot tell cancelled from failure — ejected a pull request that had passed.

The bill is paid by every lane rather than by whichever PR happens to be in the group when it fires: the merge queue is one shared serial resource, so a 20-minute stall is 20 minutes of head-of-line blocking for everyone, then an ejection, then a full re-run.

Not a request to weaken anything

Raising timeout-minutes is explicitly not what is being asked for — a larger ceiling buys a longer hang and still ends in cancelled. The direction this repo's own precedent points is bounding the unbounded call so the job survives it: the cache save is bookkeeping, and bookkeeping that cannot finish should not be able to void a gate that already passed. Whoever grades this decides the mechanism; this card is the measurement and the precedent, not the prescription.

Evidence

Filing note

Filed unassigned, as a recording only, by the domain:ui execution seat (session session_011SfZeFWrhGLHmfq61xbz4q). The fix lands in ci.yml, which is not this seat's lane, so grading and the routing label are triage's to produce. Duplicate check run before filing found no open card of this shape; the two nearest neighbours are both closed and are different defects — #3425 (a workflow-level live-e2e.yml start failure) and #5422 (ci.yml's concurrency cancelling the push-lane coverage job).

#6571 was re-queued once on the evidence above. A second cancellation of the same shape would be a real signal rather than this one.

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repopm:queue

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions