Skip to content

chore: speed up prebuild checkout with blobless partial clone - #9684

Merged
anicalbano merged 2 commits into
devfrom
fix/prebuild-timeout
Aug 12, 2026
Merged

chore: speed up prebuild checkout with blobless partial clone#9684
anicalbano merged 2 commits into
devfrom
fix/prebuild-timeout

Conversation

@decentraland-bot

@decentraland-bot decentraland-bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Increase prebuild job timeout from 10 → 20 minutes to prevent intermittent checkout timeouts on release builds (two parallel full-history clones contend for git bandwidth)
  • Skip checkout and changed-files detection when a version is already supplied (release path via workflow_call)
  • Use filter: blob:none (blobless partial clone) instead of full clone — keeps the full commit graph and tags for git describe --tags but skips expensive blob download
  • Add a Plan checkout step that centralizes the checkout predicate, consumed by Checkout, Debug Commit SHA, and Get version — prevents drift across hand-synced conditions
  • Guard Debug Commit SHA behind the checkout predicate (it runs git rev-parse)

Why blobless partial clone?

The prebuild checkout uses fetch-depth: 0 because .github/actions/version runs git describe --tags --long main, which needs the full commit graph to walk back to the nearest tag. A shallow clone (fetch-depth: 1 or 2) creates a graft boundary that breaks this.

filter: blob:none downloads the full commit graph and all tags but defers blob downloads until files are actually accessed. This gives git describe everything it needs while significantly reducing checkout time — especially important for release builds where two parallel instances compete for bandwidth.

Additionally, step-security/changed-files@v45 detects shallow clones and self-deepens them (~30s extra), negating any shallow-clone speedup. Blobless clones avoid this entirely.

Test plan

  • Verify PR builds complete prebuild checkout faster than before
  • Verify Get version step still works correctly on workflow_dispatch
  • Verify release builds (two parallel build-unitycloud.yml instances) no longer time out
  • Verify changed-files detection still works for PR builds

🤖 Generated with Claude Code

@decentraland-bot
decentraland-bot requested a review from a team as a code owner August 11, 2026 19:30
@github-actions
github-actions Bot requested a review from DafGreco August 11, 2026 19:30
@decentraland-bot decentraland-bot added the ext-contribution Identifies a contribution which was not initiated by a Unity Developer label Aug 11, 2026
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

🚦 CI Status

Build

Build skipped — no changes detected under Explorer/.

Lint

Waiting for lint to start…

Tests

Waiting for tests to start…

@github-actions

Copy link
Copy Markdown
Contributor

Slack notification sent to #explorer-ext-contributions for external review.
To re-send, delete this comment and re-add the ext-contribution label.

@claude

This comment has been minimized.

@decentraland-bot decentraland-bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@decentraland-bot decentraland-bot changed the title ci: bump prebuild timeout-minutes from 10 to 20 ci: skip unnecessary full-history checkout in prebuild Aug 12, 2026
@claude

This comment has been minimized.

@claude

This comment has been minimized.

@decentraland-bot decentraland-bot changed the title ci: skip unnecessary full-history checkout in prebuild chore: speed up prebuild checkout with blobless partial clone Aug 12, 2026
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
@claude

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

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Claude finished @decentraland-bot's task in 3m 14s —— View job


Security & workflow review — PR #9684 (re-review at 2d083c5)

  • Diff the new commit vs previous review point (8a4fae5)
  • Verify the plan step is behaviour-preserving for all event paths
  • Check ${{ }} interpolation inside the new run: block (script injection)
  • Re-run workflow security review (W.1–W.10) on the delta
  • Post verdict

Verdict: the prior LOW nit is resolved. No blockers. One new LOW (hardening), one style nit.

2d083c54 does exactly what the previous review asked for: the checkout predicate now lives in a single Plan checkout step (:258-265) whose checkout output is consumed by all three dependents (:268, :368, :376). Grep confirms no inline copy of the expression survives.

Behaviour equivalence

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) → 2d083c54explorer_changed=falseshould_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"
          fi

Fix this →

Nit (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

@anicalbano
anicalbano self-requested a review August 12, 2026 13:05
@anicalbano
anicalbano enabled auto-merge (squash) August 12, 2026 13:06
@anicalbano
anicalbano merged commit 84bbe54 into dev Aug 12, 2026
29 of 32 checks passed
@anicalbano
anicalbano deleted the fix/prebuild-timeout branch August 12, 2026 13:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ext-contribution Identifies a contribution which was not initiated by a Unity Developer new-dependency

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants