User-facing recipes for the workflows the skill encodes. For machine-readable DAGs see reference/workflows.md. For per-command flag reference see reference/commands.md.
Every recipe assumes you have a UE 5.7 branch with Engine/Extras/ushell/ushell.bat present, an active .uproject, and PowerShell access. Substitute paths for your project.
After a fresh P4 sync, get the editor running.
cmd.exe /d /s /c "call <branch>\Engine\Extras\ushell\ushell.bat --project=<uproject> && .p4 sync --nosummary && .build editor --nosummary && .run editor"What's happening:
--project=<uproject>binds the active project in the session noticeboard (each fresh ushell session is a new$FLOW_SID)..p4 sync --nosummarysyncs engine + project, honouring<root>/.p4sync.txtfilters..build editor --nosummaryruns UBT; auto-buildsShaderCompileWorker,UnrealPak,InterchangeWorker..run editorlaunchesUnrealEditor.exewith the project pre-loaded.
--nosummary suppresses the == Result: Success / Time: 0:00:42 footer so a script can parse exit codes cleanly. Without it, the verb still runs — you just see the banner.
If it fails: the exit code is 1. Re-read the == Errors and warnings == block. Do NOT fall back to Build.bat / UnrealBuildTool.exe.
This is the headline workflow. You're investigating perf at a known-bad CL. You want a .utrace file in Unreal Insights.
# Write a temp .bat (multi-command form):
@"
@echo off
call <branch>\Engine\Extras\ushell\ushell.bat --project=<uproject>
.p4 sync 1234567 --nosummary
.build editor --nosummary
.build game PS5 --nosummary
.zen snapshot list game PS5
"@ | Set-Content E:\trace-prep.bat
cmd.exe /d /s /c "E:\trace-prep.bat"After .zen snapshot list prints, decide:
- If a snapshot is available at-or-near CL 1234567 → use the fast path:
.zen snapshot get game PS5 1234567 - Otherwise → cook locally:
.cook game PS5 --nosummary
Then in either case:
.stage game PS5 auto --nosummary
.run game PS5 --trace=default,gpu
.perf insights latest
What you've done:
- Synced source to a specific CL.
- Got either pre-built or freshly-cooked content for PS5.
- Staged it (using whichever packaging style — pak or Zen — matches
Saved/Cooked/<form>/ue.projectstore). - Launched the game on the devkit with
-trace=default,gpu(the standard channel set + GPU timings). - Opened the resulting
.utracein Unreal Insights.
Why the .zen snapshot list check matters: if a snapshot exists for that CL, downloading + importing the oplog is ~50× faster than a fresh local cook. Baseline Claude doesn't know to check; the skill always does.
You got a crash report. You want to repro under VS.
cmd.exe /d /s /c "call <ushell.bat> --project=<uproject> && .p4 sync <CL>"
cmd.exe /d /s /c "call <ushell.bat> --project=<uproject> && .sln generate open"
cmd.exe /d /s /c "call <ushell.bat> --project=<uproject> && .build editor"
cmd.exe /d /s /c "call <ushell.bat> --project=<uproject> && .run editor --attach"--attach looks for a running VS instance with the matching .sln (via vs.dte.running()) and attaches the debugger. The .sln open step is critical — without it, --attach finds nothing.
Optional: add -- -WaitForDebugger to the .run editor line to halt the process at WinMain for early-crash investigation. Don't use in CI.
The editor crashes at head, was fine at CL 1234000. You want to find the culprit unattended.
Write E:\bisect-editor.bat:
@echo off
call <branch>\Engine\Extras\ushell\ushell.bat --project=<uproject>
.build editor --nosummary
if errorlevel 1 exit 90
.run editor -- -nullrhi -unattended -stdout -log -ExecCmds="Quit"
if errorlevel 1 exit 80
exit 0Then kick off the bisect:
cmd.exe /d /s /c "call <ushell.bat> --project=<uproject> && .p4 bisect 1234000 <bad-CL> -- E:\bisect-editor.bat"ushell runs the bisect protocol: at each candidate CL, syncs, runs the script, reads exit code (0=good, 80=bad, 90=failed-build — treated as "ugly", expand outward). Reports the first-bad CL.
To inspect the offending CL afterward:
.p4 cherrypick --dryrun <N>
(or via P4V: .p4 v opens it scoped to the right client).
Modern UE 5.x default. Produces a runnable cooked build with content served from a local ZenServer.
.build game ps5 --nosummary
.cook game ps5 --nosummary
.stage game ps5 zen --nosummary
.zen status # confirm ZenServer is up
.run game ps5
If .zen status reports the server is down: .zen start first.
Why style=zen vs auto: explicit zen fails if Zen isn't enabled in Project Settings; auto falls back to pak. For a Zen-enabled project, zen is more strict (good for CI). For an older project, leave it auto.
You're moving from //depot/Main to //depot/Release.
.p4 switch list # see available streams
.p4 switch Release --nosummary # switch + sync (shelves open files, restores after)
.build editor --nosummary # confirm it builds clean
.info # verify engine + project + platforms
.p4 switch requires a stream client. If yours isn't, you'll see Client 'X' is not a stream — create one with .p4 workspace <dir> //depot/Release first.
You're on //depot/Release and CL 1234567 in //depot/Main needs to come over as a hotfix.
.p4 cherrypick 1234567
That's it. ushell handles:
- Generating the cross-stream branchspec.
- Integrating the CL into a new pending CL.
- Auto-resolving safe merges.
- Clearing integration records ("edigrate") so the result looks like a native edit if the streams are unrelated.
For partial-resolve workflows: add --saferesolve to skip auto-resolve; you'll get the cherrypicked files in a pending CL needing p4 resolve -as / -am manually. Useful when you want one review for the easy files and a separate one for the contested ones.
.perf test sequence runs RunUAT RunUnreal -test=AutomatedPerfTest.SequenceTest against a staged build, using the project's AutomatedSequencePerfTestProjectSettings.MapsAndSequencesToTest config.
Preconditions:
- Project has the
AutomatedPerfTestingplugin enabled. - A staged build exists at
<project>/Saved/StagedBuilds/<cook_form>/.
.build editor && .build game win64 && .cook game win64 && .stage game win64 auto
.perf test sequence win64 perf development game <ComboName>
<ComboName> must match a ComboName= entry in your project's DefaultEngine.ini under [/Script/AutomatedPerfTesting.AutomatedSequencePerfTestProjectSettings] MapsAndSequencesToTest=(...).
subtest=all runs perf + LLM + Insights + GPUperf subtests sequentially. Drop all for single subtests:
.perf test sequence win64 llm development game <Combo> # just LLM
.perf test sequence win64 insights development game <Combo> # just Insights
Output: CSV + .utrace files under <project>/Saved/Profiling/.
.sln generate open
Generates <Project>.sln (branch-name-tagged) plus Intermediate/ProjectFiles/, then opens VS.
For Blueprint-only projects without Source/, use the lightweight variant:
.sln open tiny
Generates a minimal sln in Intermediate/ProjectFiles/TinySln/ without running UBT — useful when you just want VS for file navigation + debugging.
The generic editor commandlet entry point. ushell auto-swaps .exe → -Cmd.exe, prepends the .uproject, and inserts -run=<Name>.
.run commandlet ResavePackages -- -PackageDir=Content/Foo
.run commandlet DerivedDataCache -- -fill -unattended
.run commandlet GatherText -- -config=Config/Localization/Game.ini
.run commandlet WorldPartitionBuilder -- /Game/Maps/MyMap -Builder=Minimap
To rebuild the editor first (if your .target is stale):
.run commandlet ResavePackages --build -- -PackageDir=Content/Foo
To debug a commandlet:
.run commandlet ResavePackages --attach -- -PackageDir=Content/Foo
--attach re-dispatches through _run commandlet ... --attach which has full debugger plumbing (VS / lldb / Rider via $USHELL_DEBUGGER).
When .cook / .stage wrappers don't fit (custom shipping pipeline, plugin packaging, signing, etc.):
.uat BuildCookRun -- ^
-project=<full-path>\MyGame.uproject ^
-target=MyGame ^
-platform=Win64 ^
-clientconfig=Shipping ^
-build -cook -stage -pak -iostore -compressed -package -archive ^
-archivedirectory="D:\Out\Shipping" ^
-prereqs -nodebuginfo -utf8output -unattended -nop4 -nullrhi
Append for CI (mandatory if running headless):
-buildmachine -CrashForUAT -NoCodeSign -nosound -stdlog
-buildmachine is the magic flag — disables every modal dialog, kills the crash reporter, removes warning caps, dumps cook-timing CSV. Without it, CI hangs.
For client + dedicated server in one CI run, split into two BCR calls (cleaner archive paths):
.uat BuildCookRun -- -target=MyGame -platform=Win64 -clientconfig=Shipping -build -cook -stage -pak -iostore -compressed -archive -archivedirectory=D:\Out\Win64 ...
.uat BuildCookRun -- -target=MyGameServer -platform=Linux -serverconfig=Shipping -server -noclient -build -cook -stage -pak -iostore -compressed -archive -archivedirectory=D:\Out\LinuxServer ...
For encrypted/signed paks (Marketplace, store submission):
-encryptinifiles -signpak -signpakid=<id> -cryptokeys="<path-to-keychain.json>"
Don't use -sign alone (doesn't exist). Don't use -RunAutomationTest= under BCR (fragile; client exits before UAT polls). Use Gauntlet RunUnreal -test=UE.TargetAutomation for packaged-target tests instead.
.uat BuildPlugin -- ^
-Plugin="<full-path>\MyPlugin.uplugin" ^
-Package="D:\Out\MyPlugin" ^
-TargetPlatforms=Win64+Linux ^
-Rocket -StrictIncludes ^
-unattended -nop4
The -TargetPlatforms= is non-optional. Since UE 4.25, BuildPlugin defaults to every detected SDK platform and aborts on the first missing one. Always pass an explicit list.
For Marketplace submission: append -StrictIncludes (IWYU enforcement — Marketplace QA runs equivalent checks). -Rocket emulates the binary engine layout users have.
Binary plugins are engine-minor-locked: a plugin built against 5.4 will not load in 5.5. Build separately per minor version you want to ship.
For editor-side automation:
.uat RunUnreal -- ^
-project=MyProject ^
-test=UE.EditorAutomation -RunTest="Filter:Smoke" ^
-build=editor -platform=Win64 -configuration=Development ^
-ReportExportPath="<output>\AutoReport" ^
-WriteTestResultsForHorde ^
-MaxDuration=900 -unattended -nullrhi -CrashForUAT
For tests against a packaged client (the real CI gate):
.uat RunUnreal -- ^
-project=MyProject ^
-test=UE.TargetAutomation -RunTest="Project.Smoke" ^
-build="<archived-build-path>\WindowsClient" ^
-platform=Win64 -configuration=Shipping ^
-ReportExportPath="<output>\TestReport" ^
-WriteTestResultsForHorde ^
-MaxDuration=900 -unattended -nullrhi -stdout -FORCELOGFLUSH -CrashForUAT
Filter syntax:
Filter:Smoke— all tests withEAutomationTestFlags::SmokeFilter.Project.Combat— substring match on test name.^Project.Combat$— anchored exact match.Group:<name>— expand from ini Groups.
Report output: JSON at <ReportExportPath>/index.json + HTML. No native JUnit — post-process the JSON if downstream needs JUnit.
Drop into $USERPROFILE/.ushell/channels/mychan/:
describe.flow.py:
import flow.describe
hello = flow.describe.Command()
hello.source("cmds/hello.py", "Hello")
hello.invoke("mychan", "hello")
channel = flow.describe.Channel()
channel.parent("unreal.core")
channel.version("1")cmds/hello.py:
import unrealcmd
class Hello(unrealcmd.Cmd):
"""Says hello and prints the active project."""
name = unrealcmd.Arg("world", "Who to greet")
def main(self):
ue = self.get_unreal_context()
project_name = ue.get_project().get_name() if ue.get_project() else "(no project)"
print(f"Hello {self.args.name} — active project is {project_name}")
return 0Then:
cmd.exe /d /s /c "call <ushell.bat> --project=<uproject> && .mychan hello Aaron"
Expected: Hello Aaron — active project is MyProject.
For a real commandlet wrapper (e.g. .mychan resave <dir> running ResavePackages), see reference/channel-authoring.md — copy-paste-ready.
| Goal | Recipe |
|---|---|
| "Get me a working editor" | .p4 sync && .build editor && .run editor |
| "Trace the game at this CL on PS5" | The S2 DAG above (with .zen snapshot fast-path) |
| "Find the bad CL" | .p4 bisect <good> <bad> -- bisect.bat |
| "Reproduce under debugger" | .sln open && .run editor --attach |
| "Ship for Win64" | .uat BuildCookRun -- ... -clientconfig=Shipping (CI baseline) |
| "Test the staged client" | .uat RunUnreal -- -test=UE.TargetAutomation -RunTest=... |
| "Package my plugin" | .uat BuildPlugin -- -Plugin= -Package= -TargetPlatforms=... |
| "Run a commandlet" | .run commandlet <Name> -- <args> |
| "Add a custom verb" | drop into $USERPROFILE/.ushell/channels/<mychan>/ |
| "Clean disk space" | .p4 clean --dryrun then .p4 clean |
reference/troubleshooting.md is symptom-keyed. Common ones:
Unable to establish an Unreal context— relaunch with--project=or run.project <path>first.Client 'X' is not a stream—.p4 switch/.p4 mergedownneed stream clients.BuildPlugin tries Android/iOS/etc.— pass-TargetPlatforms=explicitly.BCR archive directory empty— Project Settings StagingDirectory is ignored; pass-stagingdirectory=and-archivedirectory=on the CLI.RunUAT: not foundfrom inside.uat— partial sync, run.p4 sync --all.
For anything not listed, check .info first to confirm context, then the relevant reference/*.md file.