Unity pipeline automation (tools and build engineer)

SkillDev tools

Automates Unity asset imports, prefab creation, scene edits, Addressables setup, and content builds.

Available today. Use it from your connected AI after setup.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use the Unity pipeline automation (tools and build engineer) skill

About this skill

Use when automating a Unity 6.3 content pipeline or build: importing an art drop (FBX, PNG) with AssetPostprocessor rules and naming conventions, generating prefabs or editing scenes from script with correct Undo and SetDirty, Addressables groups, labels and content builds, content-validation tests,

What this skill tells your AI

The instructions your AI receives, as published by scenario-labs/skills in skills/game-engines/unity/scenario-unity-pipeline-automation/SKILL.md and read by ahel’s review.

Expert level here means a pipeline that gives the same result on every machine and every run: rules applied at import time, edits made through serialization, content packed by when it loads, tests that gate the build, and a build that CI runs from a clean checkout. The stance: nothing is done by hand twice, nothing is trusted without a count, a test or a frame, and a second run changes nothing. If a sibling skill named here is missing from your available skills, ask the user to install it (npx skills add scenario-labs/skills --skill <name>); unattended, proceed from tool schemas and flag the gap.

REQUIRED BACKGROUND: scenario-unity-expert (channels, job protocol, review loop, 6.3 traps). Import its toolkit; this skill adds scripts/ut_pipeline.py and scripts/AgentKit/Pipeline/.

Status (2026-09-24, after the blind-grade refactor): every procedure ran in Unity 6000.3.21f1 on this Mac against a 200-FBX Blender drop: tests/code/unity-pipeline-automation/ (results in archive/tests/unity-pipeline-automation/live_results.jsonl).

Stance (the expert delta)

  1. Import rules are code that runs at import, and deterministic, and you prove it. Settings in OnPreprocess*, GetVersion() bumped on every code change, config declared before it is read (context.DependsOnSourceAsset), no clocks, no dictionary order, no asset writes in callbacks (6.3 Manual; Javier Abud Chavez, S2P9n5U9xVw [00:04:35], [00:19:29]). Proof: -consistencyCheck before trusting a shared cache or CI (Manual). Observed: 12,184 assets checked in 23.5 s; a random-pixel probe texture flagged, 0 of the 593 governed files. A bump or a new postprocessor reimports every asset of that importer type, packages included (1,513 textures).
  2. An edit counts only if it goes through serialization, on the exact object. Record Undo on the component that changes, before the change (Freya Holmér, pZ45O2hg_30 [03:01:01]). Observed: Undo.RecordObject(go) then a move is not undone; RecordObject(go.transform) is. A direct write on a prefab instance reverts at the next prefab update; a ScriptableObject is not saved without SetDirty.
  3. One AssetDatabase batch per stage, measured. StartAssetEditing / StopAssetEditing in try/finally (Abud Chavez [00:30:21]), and SaveAndReimport / ImportAsset inside it too. Count with the AssetDatabase counters, not the log (Abud Chavez [00:26:44]). Observed: two remap passes over 195 FBX, 19.0 s one by one vs 5.1 s batched.
  4. Pack Addressables by loading condition, not by folder, in a stable order. Assets needed under exactly the same conditions share a group; dependencies used by several groups are placed explicitly (Olle Axelsson, Valheim, Fzu2Das_1FU [00:23:08]). Unassigned shared dependencies are copied into every bundle [00:18:44]; observed, that includes URP's Lit.shader. Assign in ordinal order (6.3 Manual, Addressables determinism): 2.9.1 sorts groups and entries by GUID when it saves, but the label table keeps insertion order.
  5. Released is not unloaded, and references are loads. Memory returns when the bundle's refcount reaches zero (Addressables 2.9 manual); a build scene that references Addressable content loads it with the scene and again from the bundle (Code Monkey, C6i_JiRoIfk [00:03:32]; Axelsson [00:24:49]). Gate it; measure unloads in the player.
  6. Fix the platform at launch, the same on every stage, and ship the content you built. Target switches inside a batch job do nothing (Manual; Thom Hopper, BlVsi2cSJ88 [00:27:18]); build target is an import dependency (Abud Chavez [00:15:49]). Observed: one stage on Android reimported 1,717 assets in 52 s; switching back reused cached artifacts (60 imports). Addressables' default settings rebuild all content inside every player build.
  7. Tests are the gate. Content rules as parametric Edit Mode tests, one case per asset (Warnecke and Fine, wTiF2D0_vKA [00:24:28]), run synchronously in a pre-build callback that logs the pass [00:31:43]. Any logged error fails a test unless LogAssert.Expected (Manual); observed, that includes an import error logged by a reimport inside the test. Never -quit with -runTests.
  8. Release builds come from a clean tree, without debug defines, with provenance kept (docs digest checklist; Hopper, profile defines). Narrow named tools over generic execution (git-amend, VPjo-M6mPkE [00:03:54]; Unity, DgNrgZeJOxQ [00:07:41]).

Establish first

  • Drop contract: DCC and export settings, folder per category, suffixes, units. Default: Blender "FBX All" + "Apply Transform", _BaseColor/_Normal, a manifest.csv for extra labels.
  • Loading conditions: loaded as a set (level, biome) or one by one? Default: labels, key_by="label"; streaming uses key_by="root".
  • Targets and budgets: platforms (one -buildTarget per pipeline run), texture cap per platform, vertex budget, bundle budget per label. Defaults: 1024 px desktop, 512 mobile, 5,000 vertices.
  • Content updates: remote content after release? Archive addressables_content_state.bin per platform per release.
  • Channel and CI: is the user's editor open on the project (ut_live.channel(P))? Git repo? Accelerator? License: Personal (Hub sign-in only) or paid seat.

Workflow

  1. Preflight. ut_env.preflight(P); ut_pipeline.install(P). Editor open: the same jobs through ut_live.call or the [CliCommand] tools. Pipeline code a team's tests must call: its own Editor asmdef plus a test asmdef (install_asmdef_template). GATE: Echo ok, zero compile errors (one aborts every batch job).
  2. Scan the drop offline. up.scan_drop(drop), same rules JSON. GATE: every reject has a reason; duplicates reject every copy.
  3. Import. up.import_drop(P, drop, target=T): changed files into <root>/<Category>/<Name>/, one batch, audit. GATE: accepted = found - rejected; audit errors explained; after a postprocessor change, up.consistency_check(P, target=T) flags nothing under Assets/.
  4. Materials and prefabs. up.build_prefabs(P, target=T): URP Lit per prop, keywords by ShaderGUI.ValidateMaterial, FBX remaps with SaveAndReimport inside the batch, Prefab Variants built in a preview scene, saved only when a value changed. GATE: prefabs = accepted - size errors; a rerun rewrites 0 files (git status clean).
  5. Look at it. EditorEditJobs.BuildLineup then AgentCapture.CaptureViews with graphics=True, ut_review.review_capture, open the sheet. GATE: no magenta, black or blank flags; props upright, grounded, in scale.
  6. Addressables groups. up.assign_addressables(P, "condition", target=T). GATE: implicit_duplicates == 0; up.hard_references(P) ok.
  7. Content build. up.build_content(P, T): builder index >= 0, ClearCachedData + BuildCache.PurgeCache(false), DoNotBuildWithPlayer, layout read back, state archived. GATE: duplicated assets 0, bytes per label within budget.
  8. Tests. ut_run.run_tests(P, "EditMode", categories="PipelinePreBuild", extra_args=["-buildTarget", up.cli_target(T)]). GATE: failed 0, total equals the expected asset count.
  9. Player build. Profile copied from a committed template (PipelineBuild.CreateBuildProfile with template), ut_run.build(P, T, profile=...), or raw -activeBuildProfile ... -executeMethod AgentKit.Pipeline.PipelineBuild.BuildFromCommandLine. Release: -agentRelease (gate refuses debug defines), clean tree, up.release_ready(repo, app). GATE: AGENT_GATE ok with hard_references 0, BuildReport Succeeded.
  10. Smoke the player. up.player_smoke(app, label). GATE: loaded assets equal the manifest count; bundles unload after release.
  11. CI. up.write_ci(repo): ci/pipeline.sh (one process per stage, the same -buildTarget on each; ACCELERATOR=, CONSISTENCY=1, RELEASE=1) and GitHub Actions files. GATE: passes from a checkout without Library/; license returned if: always(); with Accelerator, the log's cache server= count above 0.

Numbers

ValueRelative toSource
5.6 to 6.7 s593 files copied and imported in one batchobserved
19.0 s vs 5.1 stwo remap passes over 195 FBX (SaveAndReimport), one by one vs in one batchobserved
11 to 12 s197 materials, 197 FBX remaps, 196 Prefab Variants, in-editor timeobserved
1,717 imports, 52 s / 60 importsfirst launch on another platform / switching back (cached artifacts)observed
1,513 textures, 17 sreimported by one GetVersion() bump of a do-nothing texture postprocessorobserved
12,184 checked, 23.5 s-consistencyCheck of this project (7,390 skipped; 2 URP shaders flagged: baseline packages)observed
0 fileschanged by a second full run (import, prefabs, groups)observed
791 tests, 10 to 16 scontent tests for 196 prefabs, 396 textures, 197 modelsobserved
13.5 / 12.9 / 4.8 MBworst label load, category / isolate / condition layouts (3 biomes)observed
5duplicated assets in the category layout (2 textures, Lit.shader, 2 fallbacks)observed
0 to 63 msbundles still loaded after the last Release (7 player runs)observed
60 to 158 s / 5 to 23 sfirst macOS Mono player build / incremental rebuilds (shared machine); 127.4 MB playerobserved
0.4 to 1.2 s vs 10 to 17 sthe same job through a resident editor's bridge vs a batch job with editor startobserved
4 GBceiling per bundle for cross-platform compatibilityAddressables 2.9 manual
30,000 verticesexample FBX budget warningUnity tutorial, qZ5NmyoxKRk [00:03:55]
38%, about 110 MBValheim memory cut from streaming, then from fewer loaded bundlesAxelsson [00:03:02], [00:30:41]
179 s / 486 sgenerated ci/pipeline.sh from a checkout without Library/ (v0.2, quiet machine / v0.1, 6 to 8 other editors)observed
0 / 8 / 6unity test exit codes: pass / tests failed (never retry) / no verdict (retry)unity-cli skill

Quality gates

  • Measurable: envelope ok, no compile errors, active_target equal on every stage; ImportDrop counts; consistency check clean under Assets/; prefab count; rerun: copied 0, git status clean; implicit_duplicates 0 and duplicated_assets 0; per-label KB; no build scene hard-referencing Addressable content; NUnit failed 0 with the expected total; AGENT_GATE ok; BuildReport Succeeded; player smoke counts; release: clean tree, provenance source.dirty false, no forbidden defines; CI script exit 0 from a clean checkout.
  • Visual: lineup sheet opened: not magenta, normal maps shading, props upright, pivots on the ground, sizes plausible next to the 1.8 m capsule.

Common mistakes

MistakeWhat it looks likeFix
importer settings in OnPostprocessTexture; code changed, GetVersion() not bumpedold settings used; machines disagreeOnPreprocess*; bump the version; -consistencyCheck
non-import keys added to the rules JSONevery governed asset reimportsseparate file (pipeline_checks.json)
Undo.RecordObject(gameObject) before moving itmove not undoablerecord the Transform
direct write on a prefab instance or a ScriptableObjectreverts at the next prefab update / file unchangedRecordPrefabInstancePropertyModifications / SetDirty
StartAssetEditing without finally; SaveAndReimport per FBX outside a batchfrozen database; 4x slower remapstry/finally; reimport inside the batch
stages without the same -buildTargeteach stage reimports textures and shadersthe same target on every stage, tests included
one group per folder with shared texturesthe same texture and shader in every bundlecondition packing; build layout check
labels added in file or HashSet orderthe settings file differs between machinesordinal order
a build scene references an Addressable prefabloaded twice, bundles bypassedHardReferences in the gate
content built, then a default player buildcontent rebuilt, archived state staleDoNotBuildWithPlayer + gate
SetActiveBuildProfile in a batch job; profile from the internal APIold target or defines; breaks on upgrade-activeBuildProfile; copy a committed template
a test reimports an asset whose importer logs an error"Unhandled log message", test failsLogAssert.Expect
tests in a test asmdef calling code in Assembly-CSharp-EditorCS0103, every batch job abortspipeline code in its own Editor asmdef
-quit with Accelerator, no wait flag; server unreachableuploads lost; job passes silently-cacheServerWaitForUploadCompletion; check cache server=
release from a dirty treeprovenance dirty: true, the CLI still buildsclean tree, RELEASE=1, gitignore Addressables' link.xml
Blender default FBX exportroot rotated 90 degrees, scale 100FBX All + Apply Transform (scenario-blender-expert)
duplicate name resolved by orderthe wrong asset silently winsreject every copy, ask
Addressables settings created inside a jobthe next run rewrites the settings file onceset the two provider types after GetSettings(true)
-quit with -runTests; relative --output-pathno XML; build outside the projectomit -quit; absolute path

Handoffs

  • Receives: FBX and textures with export settings from scenario-blender-expert, scenario-maya-expert, scenario-3d, scenario-textures; code layout and asmdefs from scenario-unity-architecture; which props load together from scenario-unity-world-building.
  • Delivers: to scenario-unity-performance the build report, bundle sizes, per-label loads; to scenario-unity-mobile texture formats (this skill sets size caps only) and signed builds; to scenario-unity-web Web builds (no WaitForCompletion on WebGL); to scenario-unity-rendering-lighting lightmap UV needs; to the lead: rules JSON, props manifest, envelopes, NUnit totals, layout JSON, BuildReport, provenance, contact sheet.

Unity 6.3 notes

  • Addressables 2.9.1 is this editor's default (folder keys and await handle are 4.0); its postprocessor adopts any imported group asset into the default settings. Content Directories replace AssetBundle builds in 6.6.
  • IPreprocessBuildWithContext also fires for AssetBundle builds: filter with BuildPipeline.isBuildingPlayer.
  • No public profile-creation API before 6.5; AssetDatabase counters are public in UnityEditor.Experimental.AssetDatabaseExperimental.counters.
  • The Unity CLI is beta; unity build writes its provenance manifest inside the .app on macOS. Personal licenses activate only through Hub sign-in.

References

Signals

GitHub stars
681
Forks
82
Last commit
Sep 2026
Advanced
Item type
skill
Key
scenario-unity-pipeline-automation
Source
github.com/scenario-labs/skills