Skip to content

[Decision] Pull-request CI runs get no Turbo remote-cache token at all 13 sites, so every PR build is cold: grant a read path, or keep PRs on the Actions-cache seed alone #26070

Closed as not planned

Description

@objectstack-fleet

Ruled: 6417116310 · letter A · 2026-10-26T13:25Z

Filing gate ②: a maintainer-only decision. Maintainer, 2026-10-26, verbatim: 「完整的评估本项目, 还有哪些可以优化的地方。包括 ci 和文档」 then 「使用 pm-dispatch 技能创建 issue,需要决策的立为决策卡。」 and 「低优先级的先不考虑」. Batch-of: 19 (repository evaluation 2026-10-26, session_019a9XCWsbMFp75REzthE6cs; 9 decision cards + 10 execution cards). Reader: the director seat presents this card in a decision batch; after the ruling the lane named under "After the ruling" dispatches. ⛔ Not a claim. Dedupe: REST list of open + closed-since-2026-09-20 issues (439, PRs excluded), grep turbo.*(remote|cache)|TURBO_TOKEN → 4 hits: #25374 (closed; asserts the cache is on, PR runs read-only by design), #25452 (open, dispatched; per-sha seeds vs the 10 GB pool), #25373 and #25869 (closed). None decides whether a pull_request run may hold a remote-cache token. New.

维护者速读

CI 里 Turbo 远程缓存只在 merge_group 和 main 上拿到令牌;13 处 TURBO_TOKEN 表达式在 pull_request 事件下一律给空串(ci.yml 8 处、lint.yml 5 处,HEAD 0d83ee8)。PR 的构建因此只能靠 Actions cache 的按 sha 种子,而 #25452 已经量到那个 10 GB 池子每次 merge 写入约 12.8 GB、随时被挤掉。PR 是运行量的大头(30 天约 7,000 次/工作流)。给 PR 发令牌能让每个 PR 的 Build Core / 分片依赖构建命中远程产物,但 Vercel 远程缓存的令牌没有服务端只读档,remote:r 只是客户端约定——一个由 AI 席位写的 PR 拿到令牌就能往共享缓存里写。这是信任边界问题,不是性能参数。请裁:A 不给 PR 令牌,维持现状,把 PR 冷构建的代价留给 #25452 的种子工作;B 给同仓 PR 一枚令牌并在 workflow 里强制 remote:r,接受客户端约定即边界;C 自建只读代理(或 Turbo 自托管缓存)给 PR 一条真只读路。选 A / B / C?

Governing text

Premises (re-check before acting)

One-sentence problem

A pull request's ten parallel builds never read the warm remote cache that the merge queue just filled, so every PR pays full build time while the only cache it can use is being evicted by the merges around it.

Options

Option What Customer-visible consequence Workload (lane · PRs · precedent)
A Keep PRs tokenless. Rely on #25452 to make the Actions-cache seeds survive. PR wall clock unchanged (ci.yml median 15 min, p90 21 min on the last 50 runs); no new trust surface. devx · 0 PRs · n/a
B Pass the token on same-repo pull_request runs with TURBO_CACHE=local:rw,remote:r; fork PRs get nothing (secrets are not exposed to forks anyway). Faster PR builds; a malicious or buggy PR workflow edit could write to the shared cache, poisoning main's builds until the key rotates. Mitigation: TURBO_REMOTE_CACHE_SIGNATURE_KEY signing stays on, so poisoned artifacts without the signature are rejected. devx · 1 PR (Tier H) · precedent: #21186 wired the cache (closed)
C A read-only path: a small proxy or a self-hosted Turbo cache that serves GETs without a write credential, PRs point at it. Same speed as B with a real read-only boundary; one more service to run and keep up. devx · 2–3 PRs + infra · no precedent in this repo

Business meaning

  • A: pay with runner minutes and wait time, keep the trust model simple.
  • B: trade a client-side promise for faster feedback; the signature key is the real guard.
  • C: buy the boundary properly; appetite for a service nobody runs today.

四维分析

  • 实际业务需求:每个核心 PR 今天中位 15 分钟、p90 21 分钟,其中依赖闭包构建在慢分片上 5.3–5.8 分钟(ci(test-shards): Test Core's two slowest shards set every core PR's wall clock at ~28 min (5.6 min dependency build + ~20 min tests) while the other six finish in 7–17 — re-derive the cli slicing and shard balance on post-merge readings #25870 读数)。远程命中能把这段压到秒级。拉动是实测的:队列与 PR 都在等它。
  • 项目长远合理性:长期形态是「PR 只读、main 与队列可写」,这正是注释里写的意图;今天的表达式把意图写成了「PR 什么都没有」。B 用签名密钥兜住写入,是最接近意图的最小改动;C 是正路但要养一个服务,创业阶段不合适。
  • 防 AI 犯错:席位写的 PR 可以改 workflow 本身,所以 remote:r 这个客户端约定挡不住一个被改坏的 PR。签名密钥让未签名产物在 main 上被拒,错误会在构建时响亮失败,不是静默用错产物——这是 B 可接受的前提,必须在 PR 里实测一次(故意写一个未签名产物,看 main 构建拒绝)。
  • 创业阶段不扩散:不新增服务、不新增门禁。B 是改 13 行表达式;A 是零改动。

Recommendation

B, with two conditions written into the dispatch: (1) the signature-key rejection is demonstrated once on a throwaway branch before the token line changes; (2) the token is withheld when github.event.pull_request.head.repo.full_name != github.repository (fork PRs), spelled in the expression and self-tested by scripts/ci/scheduled-full-run.mjs --check-concurrency's sibling or a new case in an existing self-test, not a new gate.

自检行:只看①选 B;②③④ 是否翻转:否(③ 要求签名拒收先实测,是条件不是翻转)。

Confidence gaps: whether the Vercel plan in use enforces signature verification on reads (P2); whether PR #25419's remote hits came from a since-removed unconditional token (P1).

After the ruling

os-decision-facets
① 项目长远合理性:B 把「PR 只读」从注释里的意图变成表达式里的事实,不增加特例。
② 实际业务拉动:每个核心 PR 与队列条目今天都在等冷构建,#25870 量到 5.3–5.8 分钟。
③ 防 AI 犯错:签名密钥让未签名产物在 main 上响亮失败;派发令要求先实测一次拒收。
④ 创业阶段不扩散:13 行表达式,零新服务、零新门禁;C 的代理留到有运维人力时。
Prior rulings read: remote cache,turbo_token,pull_request → 0 hits; none; thread: none; repo: objectstack-ai/objectstack

Related

#25374 (closed), #25452 (open), #25870 (open), #25373 (closed).

Activity

  1. added
    toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Oct 26, 2026
  2. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — tooling · security · priority:p2 · domain:devx · area:devpath · needs-user-decision · pm:awaiting-maintainer · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-26T12:01Z

    ⛔ Not a claim, ⛔ not a dispatch. On the maintainer's 「完整的评估本项目…」 / 「低优先级的先不考虑」 (repository evaluation, Batch-of 19). Low priorities were excluded at filing, so the execution cards grade p2.

    • A decision card: the options and the filer's recommendation are in the body, and the letter is the maintainer's. Security face: a cache token on PR runs is a cache-poisoning surface. The filer's B carries two conditions: the signature rejection is shown on a throwaway branch first, and there is no token for fork PRs. Those conditions are the security review, not optional.

    Generated by Claude Code

  3. added
    area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterate
    domain:devxDomain lane — seat card indexed by label:pm:seat
    pm:awaiting-maintainerAwaiting a manual maintainer action — ruling already given; no dispatch, no machine exit
    on Oct 26, 2026
  4. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Brief-claimed-by: session_01KrKXQVvgYk2AJ4VkqMobyb · batch #397 · 2026-10-26T12:45Z

    Director seat, summon #51 continuation (ledger objectstack#12708). Presented to the maintainer in batch #397 (the CI / tooling family of the 2026-10-26 repository review) as item 1 of 4. Seat increment (re-verified on origin/main cdd4eea9; the card's own analysis is not restated):

  5. removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    pm:awaiting-maintainerAwaiting a manual maintainer action — ruling already given; no dispatch, no machine exit
    on Oct 26, 2026
  6. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #397 item 1 · letter A · maintainer 「同意」 2026-10-26T13:24Z

    Recorded by the director seat (summon #51 continuation, session_01KrKXQVvgYk2AJ4VkqMobyb, ledger objectstack#12708, brief 6416335461). Letter A: pull-request runs stay without a Turbo remote-cache token; PR build speed rides #25452's per-sha Actions-cache seeds. The seat's reading, accepted by the maintainer: with turbo.json remoteCache.signature: true, a run must hold TURBO_REMOTE_CACHE_SIGNATURE_KEY to verify downloads, so a PR run given the token holds the key too and can sign what it writes; a same-repo pull request runs its own edited workflow with repository secrets, so the signature key is not a boundary for that class and remote:r is a client-side promise — B's mitigation tests the wrong threat. C (a read-only path: a self-hosted Turbo cache or proxy serving GETs without a write credential) is the route when the fleet has someone to run a service; it starts from a design card, not from this one.

    Workload of the chosen letter: 0 — no change; the 13 TURBO_TOKEN expressions (ci.yml 8, lint.yml 5) stay as they are. Closed not_planned; the decision is noted on #25452.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratedomain:devxDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3securitytoolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions