Lesson 268: Unity CI Concurrency Cancel Library Poison Receipt on BUILD_RECEIPT (2026)
Direct answer: Before November 2026 fest Addressables promotion, promote ci_cancel_safe_receipt_v1.json with X1–X6 gates—split concurrency groups so lint may cancel but Addressables may not mid-catalog, add rm -rf Library cleanup with if: always() after cancel weeks, document X4 cancel repro, and fail-close addressables_build_exclusive_ok on BUILD_RECEIPT. Pair Concurrency cancel preflight (Guide #30), 3-day no false green challenge scenario F, and cousin Lesson 261 (unity_ci_library_cache_receipt_v1 cache key—not cancel boundaries).

Why this matters now (November CI cancel edges)
November 2026 teams add concurrency: cancel-in-progress on main to stop double CI—then a fast lint job and a slow Addressables player build share one group. Cancel fires during AddressablesPlayerBuildProcessor; the next job restores a half-written Library/ and passes compile until fest players hit InvalidKeyException. Lesson 267 wires async playtest CSV honesty; 268 wires CI cancel boundaries before Wednesday smoke.
Library cache preflight owns ContentState hash in the cache key; 268 owns cancel policy and post-cancel cleanup on BUILD_RECEIPT.
Beginner path (split groups → cleanup → receipt)
| Step | Action | Success check |
|---|---|---|
| 1 | Put lint and Addressables in different concurrency groups |
X1 group names logged |
| 2 | Set cancel-in-progress: false on Addressables job |
X2 pass |
| 3 | Add cleanup-after-addressables with rm -rf Library |
X3 if: always() |
| 4 | Reproduce two cancels + third green build | X4 cancel repro log |
| 5 | Wire fail-closed jq before merge | X5 addressables_build_exclusive_ok |
| 6 | File receipt + BUILD_RECEIPT column | ci_cancel_safe_ok: true |
Time: ~55 minutes first November cancel week; ~15 minutes when workflow template exists.
Developer path (gates X1–X6)
| Gate | Check | Fail when |
|---|---|---|
| X1 | Split concurrency groups in workflow YAML | Lint and Addressables share one group |
| X2 | Addressables job has no mid-build cancel | cancel-in-progress: true on catalog job |
| X3 | Post-cancel rm -rf Library step |
Cleanup skipped when job cancelled |
| X4 | Cancel mid-build repro documented | No log of forced cancel + recovery |
| X5 | Fail-closed promotion jq | Merge when addressables_build_exclusive_ok false |
| X6 | ci_cancel_safe_receipt_v1.json |
ci_cancel_safe_ok false at promote |
Align with Guide #30 preflight—guide = ninety-second gate; 268 = BUILD_RECEIPT milestone.
Cousin receipt crosswalk
| Field | Cousin (261) | Cousin (3-day F) | This lesson (268) |
|---|---|---|---|
| Schema | unity_ci_library_cache_receipt_v1 |
addressables_ci_cache_receipt_v1 |
ci_cancel_safe_receipt_v1 |
| Scope | Cache key + ContentState hash | Three-day cache ladder | concurrency split + post-cancel cleanup |
| Path | release-evidence/unity/ci-cache/ |
release-evidence/unity/ci-cache-ladder/ |
release-evidence/unity/ci-cancel/ |
Do not merge schemas—reference paths in cousin_receipts only.
X1 — Split workflow concurrency groups
# .github/workflows/unity-fast.yml
concurrency:
group: unity-fast-${{ github.ref }}
cancel-in-progress: true
# .github/workflows/unity-addressables-player.yml
concurrency:
group: unity-addressables-exclusive-${{ github.ref }}
cancel-in-progress: false
Rule: Never share one concurrency.group between fast jobs and Addressables player jobs.
X2 — Disable cancel on Addressables-exclusive job
Single workflow file alternative:
jobs:
addressables-build:
concurrency:
group: unity-addressables-exclusive-${{ github.ref }}
cancel-in-progress: false
Document job name in receipt—Thursday row review column addressables_build_exclusive_ok.
X3 — Post-cancel Library cleanup
cleanup-after-addressables:
needs: addressables-build
if: always()
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: rm -rf Library Temp
Run even when addressables-build is cancelled—partial StreamingAssets/aa must not cache forward.
X4 — Cancel repro matrix
- Push commit A; start Addressables build.
- Push commit B while A runs (trigger cancel if misconfigured).
- Let job C start; verify
Library/was cleaned (X3). - Player smoke on artifact from C—catalog loads without
InvalidKeyException.
Log cancel count and job ids in receipt cancel_repro_log. Pair 3-day challenge scenario F.
X5 — Fail-closed promotion jq (CI tail)
jq -e '.addressables_build_exclusive_ok == true' release-evidence/unity/ci-cancel/CI_CANCEL_SAFE_RECEIPT.json
Block merge to fest branch when receipt missing after any cancel week—pair Wednesday smoke.
ci_cancel_safe_receipt_v1.json
{
"schema": "ci_cancel_safe_receipt_v1",
"build_label": "november-closeout-2026-rc2",
"workflow_paths": {
"fast": ".github/workflows/unity-fast.yml",
"addressables_exclusive": ".github/workflows/unity-addressables-player.yml"
},
"concurrency_groups": {
"fast": "unity-fast-${{ github.ref }}",
"addressables_exclusive": "unity-addressables-exclusive-${{ github.ref }}"
},
"addressables_job": "addressables-build",
"cancel_in_progress_on_addressables": false,
"post_cancel_cleanup_job": "cleanup-after-addressables",
"cancel_repro_log": {
"forced_cancel_count": 2,
"recovery_job_id": "987654321",
"player_smoke_invalid_key": false
},
"cousin_receipts": {
"library_cache": "release-evidence/unity/ci-cache/UNITY_CI_LIBRARY_CACHE_RECEIPT.json",
"playtest_csv_ingest": "release-evidence/playtest/playtest_csv_ingest_receipt_v1.json"
},
"gates": {
"X1_split_groups": "pass",
"X2_no_cancel_on_addressables": "pass",
"X3_post_cancel_cleanup": "pass",
"X4_cancel_repro": "pass",
"X5_fail_closed": "pass",
"X6_receipt": "pass"
},
"addressables_build_exclusive_ok": true,
"ci_cancel_safe_ok": true,
"content_update_promotion_allowed": true
}
Pin under release-evidence/unity/ci-cancel/CI_CANCEL_SAFE_RECEIPT.json.
BUILD_RECEIPT columns (November lane)
| Column | Source | Pass when |
|---|---|---|
addressables_build_exclusive_ok |
X5 jq | true |
ci_cancel_safe_ok |
X6 receipt | true |
library_cache_bust_ok |
Lesson 261 cousin | true when strip week |
wednesday_smoke_allowed |
Lesson 267 cousin | true |
Key takeaways
- Split concurrency groups—lint may cancel; Addressables may not mid-catalog.
rm -rf Libraryafter cancel withif: always()—non-negotiable on fest months.- Fail-closed
addressables_build_exclusive_okbefore branch promotion. - Run Library cache preflight before cancel policy on strip weeks.
- 3-day challenge scenario F validates cancel + clean ladder.
- Invalid Key help triages runtime; 268 prevents false-green promotion.
- 15-free GitHub Actions CI recipes bookmark build graphs—cancel policy is separate column.
- Cousin Lesson 261 owns cache key material—not cancel boundaries.
Common mistakes
- One
concurrency: group: unity-mainfor lint + Addressables (X1 fail). cancel-in-progress: trueon Addressables job during catalog writes (X2 fail).- Skipping cleanup when job status is
cancelled(X3 fail). - Blaming cache key when cancel poisoned
Library/(Lesson 261 cousin). - Promoting after cancel week without X4 repro on clean runner.
- Merging
ci_cancel_safe_receiptwithunity_ci_library_cache_receiptschemas.
Troubleshooting
| Symptom | Lane |
|---|---|
| Random InvalidKey after busy CI day | X1 split + X3 cleanup |
| Cancelled job, next job fast but player broken | X3 if: always() missing |
| Only fails after second push same hour | X2 disable cancel on Addressables |
| Cache key correct, still stale | Library cache C3 + this X3 |
| CI green after cancel, smoke fails Wednesday | File 268 before smoke—not only Help #30 forward |
Mini exercise (50 minutes)
- Audit workflow YAML—log X1 group names.
- Misconfigure shared group once—confirm X4 repro catches poison.
- Add X3 cleanup job with
if: always(). - Wire X5 jq gate in workflow tail.
- File receipt; BUILD_RECEIPT GREEN for
ci_cancel_safe_ok. - Crosswalk cousin Lesson 261 path in
cousin_receipts.
Continuity — November 2026 fest close-out + playtest triage (266–276)
| Lesson | Receipt focus |
|---|---|
| 267 | Discord CSV ingest |
| 268 (this) | Unity CI cancel poison |
| 269 | Godot VDF LF |
| 270 | GameMaker setlive dry-run (forward) |
Previous: Lesson 267 — Discord playtest thread CSV ingest receipt
Next: Lesson 269 — Godot export VDF LF line ending steamcmd receipt
FAQ
Same as Lesson 261?
261 = actions/cache key + ContentState hash; 268 = concurrency split + post-cancel Library/ cleanup.
Same as 3-day challenge scenario F?
Challenge owns F1–F6 ladder calendar; 268 owns ci_cancel_safe_receipt_v1 BUILD_RECEIPT column.
Same as Guide #30?
Guide = ninety-second preflight; 268 = course milestone with promotion jq + cancel repro log.
Help #30 when it ships?
Fix lane when cancel already corrupted Library/; 268 prevents promotion without X1–X6.
Resource #28 when it ships?
Bookmark list for safe concurrency patterns—see 12 patterns listicle until Resource row publishes.
Can we delete actions/cache instead?
No—run Lesson 261 cache key lane and 268 cancel lane.
Does this replace Wednesday smoke?
No—binary smoke is separate; 268 stops false-green CI before smoke runs.
November CI lies when cancel-in-progress interrupts Addressables—split groups, clean Library/ after cancel, fail-closed addressables_build_exclusive_ok, then promote.