chore: speed up prebuild checkout with blobless partial clone - #9684
Conversation
|
Slack notification sent to #explorer-ext-contributions for external review. |
This comment has been minimized.
This comment has been minimized.
decentraland-bot
left a comment
There was a problem hiding this comment.
Review — PR #9684: ci: bump prebuild timeout-minutes from 10 to 20
STEP 2 — Root-cause check: ✅ PASS
The problem is real and well-documented: the prebuild job's full-history checkout (fetch-depth: 0) on a repo this size consistently exceeds 10 minutes on GitHub-hosted runners. The fix raises the timeout ceiling — this addresses the cause directly rather than a symptom. The 20-minute value provides reasonable headroom while staying well below the build job's 510-minute ceiling.
STEP 3 — Design & integration: N/A
CI workflow config only — no new types, systems, or runtime code. No lifecycle, ECS, or architectural concerns apply.
STEP 4 — Member audit: N/A
No code members added or changed.
STEP 5 — Line-level review
The single changed line is correct. No issues found in the diff.
[P2] PR title uses ci: which is not a recognized type. The repo's action-semantic-pull-request configuration only allows: break, chore, docs, feat, fix, refactor, revert, style, test, release. The semantic / title-matches-convention CI check is currently failing because of this. Rename the PR title to use chore: instead — e.g. chore: bump prebuild timeout-minutes from 10 to 20.
Security review
No security concerns — this is a CI timeout configuration change with no runtime, auth, or data implications.
STEP 6–9 — Verdict
REVIEW_RESULT: PASS ✅
COMPLEXITY: SIMPLE
COMPLEXITY_REASON: Single-line CI workflow config change (timeout bump), no runtime code
QA_REQUIRED: NO
Reviewed by Jarvis 🤖 · Requested via GitHub
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
d7eea67 to
1debbcb
Compare
The prebuild job checkout (fetch-depth: 0) was intermittently timing out on release builds where two parallel instances contend for git bandwidth. PR/dev checkouts are consistently ~40-45s, but release launcher checkouts hit 2-10 min in 4 of the last 7 releases. Changes: - Bump timeout-minutes 10 -> 20 as a safety net - Add filter: blob:none (blobless partial clone) to keep the full commit graph and tags for git describe --tags while skipping the expensive blob download - Skip checkout entirely for release builds where version is supplied (no prebuild step needs the repo on that path) - Guard changed-files behind pull_request event check (its output is unused for non-PR triggers) - Guard Debug Commit SHA behind the same checkout condition
This comment has been minimized.
This comment has been minimized.
Address reviewer nit: the checkout condition was repeated in three places (Checkout, Debug Commit SHA, Get version) with slightly different formulations. Add a Plan checkout step that writes checkout=true/false to GITHUB_OUTPUT, then reference steps.plan.outputs.checkout from all three consumers. This makes the coupling explicit and prevents drift that would surface as an opaque action.yml-not-found error.
|
Claude finished @decentraland-bot's task in 3m 14s —— View job Security & workflow review — PR #9684 (re-review at
|
| Step | Before (8a4fae5) |
After (2d083c5) |
Diverges? |
|---|---|---|---|
Checkout |
PR || (A && B) inline |
plan.checkout |
no — same predicate, relocated |
Debug Commit SHA |
should_build && (PR || (A && B)) |
should_build && plan.checkout |
no |
Get version |
should_build && A && B |
should_build && plan.checkout |
widened to should_build && (PR || (A && B)) |
The Get version widening is safe and is in fact the right direction. On pull_request, github.event.inputs.version and inputs.version are both '' (no version input is defined on workflow_dispatch at :16-102; the inputs context is empty outside workflow_call/workflow_dispatch), so A && B was already true there — the two predicates coincide on every reachable event.
Better still, the design is now correct by construction against the job output at :249:
version: ${{ github.event.inputs.version || inputs.version || steps.get_version.outputs.full_version }}checkout == false ⟺ (not a PR) ∧ (some input version is non-empty) ⟹ the || chain short-circuits before reaching full_version. So the only consumer of get_version can never need it on a run where get_version is skipped. That was previously an implicit hand-maintained invariant; it now falls out of one predicate. Verified by grep: steps.get_version.outputs.* has exactly one reference in the file.
Caller check re-confirmed — only two workflows actually uses: this one, build-release-main.yml:44-49 and build-profile-nightly.yml:74-79, both passing a version from a get-info job. visual-regression.yml and create-release-branch.yml only mention the filename inside gh api strings — not callers.
Measured on this exact commit
Prebuild for 2d083c5 (job 94121579650, Unity Cloud Build ✅):
| Phase | Span | Duration |
|---|---|---|
git fetch … --filter=blob:none (incl. 169 v[0-9]* tags) |
12:58:27.226 → 12:58:30.128 | 2.9 s |
git checkout --progress --force (lazy blob materialization) |
12:58:30.174 → 12:59:19.943 | 49.8 s |
changed-files self-deepening |
12:59:19.943 → 12:59:19.982 | 0.04 s — no-op |
| Whole prebuild job | 12:58:26.8 → 12:59:24.3 | ~58 s |
Blob materialization was 24 s on the previous run and 50 s here — runner variance, not a regression from this commit. Either way timeout-minutes: 20 is pure headroom. changed-files again resolved correctly on the partial clone (1debbcbd (dev) → 2d083c54 → explorer_changed=false → should_build=false, correct for a workflow-only PR).
Note this PR's own CI does not exercise Get version / Debug Commit SHA — both are gated on should_build == 'true', which is false here. Those paths are covered by the next push to dev.
🔵 LOW (new in this commit) — interpolate via env:, not into the shell body
:261 splices three ${{ }} values straight into a bash if. The runner log shows the textual substitution plainly:
Run if [[ "pull_request" == "pull_request" ]] || { [[ -z "" ]] && [[ -z "" ]]; }; then
The values are substituted before bash parses the line, so a value containing " or $(…) would change the script's meaning rather than being compared by it. Not exploitable as written: github.event_name is a GitHub enum; github.event.inputs.version can never be non-empty (no such workflow_dispatch input exists); and inputs.version reaches this workflow only from the two in-repo callers above, both deriving it from .github/actions/version (git describe --tags) — no fork-controllable path. Filed as hardening, not a vulnerability.
- name: Plan checkout
id: plan
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
DISPATCH_VERSION: ${{ github.event.inputs.version }}
CALL_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
if [[ "$EVENT_NAME" == "pull_request" ]] || { [[ -z "$DISPATCH_VERSION" ]] && [[ -z "$CALL_VERSION" ]]; }; then
echo "checkout=true" >> "$GITHUB_OUTPUT"
else
echo "checkout=false" >> "$GITHUB_OUTPUT"
fiNit (folded into the snippet above): the step omits shell: bash and set -euo pipefail, unlike the adjacent Decide whether to build step (:284-286). No bug — the runner default on ubuntu-latest is already bash -e, so [[ ]] parses fine — purely consistency.
Workflow security review (STEP W)
[PR] The full delta vs dev touches no trigger, no permissions: block, no secrets.* reference, no uses: ref, and no $GITHUB_OUTPUT heredoc (the new step writes plain key=value, so W.2 does not apply). The new run: body does consume interpolated context — assessed above as W-adjacent hardening only, since none of the three values is fork-controllable. Trigger remains pull_request, not pull_request_target; prebuild references no secrets. step-security/changed-files stays SHA-pinned (3dbe17c7…). W.1, W.3–W.10: no new findings.
Pre-existing and unchanged by this PR (out of scope, noted for the record): nick-fields/retry@v3 holding secrets on a mutable tag — W.7a; floating npx @dcl/cdn-uploader@next with EXPLORER_TEAM_AWS_* and npx --yes @sentry/cli with SENTRY_AUTH_TOKEN; no permissions: block in the file — W.10.
[PR] Dependency check — not applicable. The only changed file is .github/workflows/build-unitycloud.yml. No Packages/manifest.json, no packages-lock.json, no Assets/**, no .asmdef, no plugin binaries, no install scripts, no new binaries. STEP 1–3 have nothing to assess; the new-dependency label remains a false positive from the path matcher.
| File | Version | Source | Type | Scope | Risk | Confidence |
|---|---|---|---|---|---|---|
.github/workflows/build-unitycloud.yml |
— | in-repo | CI config | build-time | LOW | HIGH |
CI note: Enforce QA and DEV Approvals is red only because the PR lacks approvals — not a code signal. Unity Cloud Build, Validate PR Title, and the rest are green.
DEPENDENCY_REVIEW: PASS
I made no code changes — this was a review. I also can't modify files under .github/workflows/ even if asked (GitHub App permissions don't allow workflow modifications); see the FAQ.
· branch fix/prebuild-timeout
Summary
workflow_call)filter: blob:none(blobless partial clone) instead of full clone — keeps the full commit graph and tags forgit describe --tagsbut skips expensive blob downloadPlan checkoutstep that centralizes the checkout predicate, consumed byCheckout,Debug Commit SHA, andGet version— prevents drift across hand-synced conditionsDebug Commit SHAbehind the checkout predicate (it runsgit rev-parse)Why blobless partial clone?
The prebuild checkout uses
fetch-depth: 0because.github/actions/versionrunsgit describe --tags --long main, which needs the full commit graph to walk back to the nearest tag. A shallow clone (fetch-depth: 1or2) creates a graft boundary that breaks this.filter: blob:nonedownloads the full commit graph and all tags but defers blob downloads until files are actually accessed. This givesgit describeeverything it needs while significantly reducing checkout time — especially important for release builds where two parallel instances compete for bandwidth.Additionally,
step-security/changed-files@v45detects shallow clones and self-deepens them (~30s extra), negating any shallow-clone speedup. Blobless clones avoid this entirely.Test plan
Get versionstep still works correctly onworkflow_dispatchbuild-unitycloud.ymlinstances) no longer time out🤖 Generated with Claude Code