A version control system that doesn't talk to your build farm is half a system. The build farm is where correctness gets verified — where a commit that looks fine in someone's local workspace gets tested against the full shader compilation matrix, the packaging pipeline, and the automated test suite. If your farm is syncing from stale or non-canonical workspace state, every green build is a guess.
This post walks through how to wire Diversion's post-sync hooks into an Unreal Automation Tool build farm — specifically the webhook configuration, the build trigger logic, and how to avoid build storms when multiple developers push in quick succession.
The Core Integration Model
Diversion exposes a webhook system that fires on sync completion events. When a sync lands on a branch you've configured for CI, the webhook payload includes the branch name, the commit hash synced to, the list of changed file paths, and the user who triggered the sync.
A minimal webhook payload looks like:
{
"event": "sync.completed",
"branch": "main",
"commit": "a8f3d219",
"changed_paths": [
"Content/Maps/Level_Harbor.umap",
"Content/Blueprints/UI/BP_HUD.uasset",
"Source/GameMode/HarborGameMode.cpp"
],
"triggered_by": "maya.chen",
"timestamp": "2026-01-08T09:14:32Z"
}
Your build farm coordinator receives this payload at the registered endpoint and decides what to build. The decision logic here is the key engineering surface — not all syncs should trigger full builds, and not all changed paths warrant the same build targets.
Mapping Changed Paths to Build Targets
Unreal Automation Tool (UAT) supports granular build target specification. Rather than triggering a full cook-and-package on every sync, a well-configured integration maps file path patterns to minimal build target sets:
PATH_TO_BUILD_TARGET = {
"Source/**/*.cpp": ["compile_target_PS5", "compile_target_PC"],
"Source/**/*.h": ["compile_target_PS5", "compile_target_PC"],
"Content/Maps/*.umap": ["cook_target_PC", "map_check"],
"Content/**/*.uasset": ["cook_target_PC"],
"Config/**/*.ini": ["config_validation"],
"*.cs": ["build_scripts_lint"],
}
When the webhook payload arrives, your coordinator iterates changed_paths, resolves the union of all matching build targets, and queues those targets for the incoming commit hash. A sync that only touched a Blueprint gets a cook, not a full shader compilation. A sync that touched C++ source gets the full compile pipeline.
This path-to-target mapping is specific to your project's structure and needs to be maintained as your depot grows. The overhead of maintaining it is offset by dramatically faster feedback cycles — a Blueprint-only change can go from sync to cook-verified in 8–12 minutes instead of waiting for a 90-minute full build.
Build Storm Prevention
The build storm problem: it's a Monday morning and five engineers all push changes within a 20-minute window. Without debounce logic, your build coordinator queues five separate builds, each starting from scratch with the workspace state at their respective commit. The farm spends 7.5 hours building the same engine, different game content.
The standard approach to this is a build queue with per-branch coalescing. Rather than starting a build immediately on every sync event, the coordinator holds each event for a configurable window (2–5 minutes) and coalesces all events on the same branch into a single build against the most recent commit. If three syncs arrive on main within 3 minutes, one build runs against the third commit — not three builds against each.
The coalescing window has a tuneable tradeoff: too long and developers wait unnecessarily for build feedback on solo commits; too short and you miss coalescing a burst. For a 20-30 person team with a busy main branch, 3–5 minutes tends to be the practical sweet spot.
There's also the case where a build is already running when new commits arrive. Rather than queueing a second full build, most teams cancel the in-progress build and restart against the latest commit. This avoids building intermediate commits that will never ship, but it means a developer who pushed first may wait longer for feedback if someone pushed after them before the build finished. Document this behavior explicitly — developers who don't know the queue policy will assume their build was lost.
Build Result Feedback Loop
A build farm integration is only useful if developers can see the result quickly and clearly. The minimum viable feedback loop:
- Commit status annotation on the triggering sync — the Diversion API exposes a commit status endpoint where your build coordinator can POST build results back to the commit record. Status values:
pending,running,passed,failed. - Build log URL in the status payload so developers can click through to the full UAT output without asking a build engineer.
- Slack (or equivalent) notification to the committer on failure, with the first failing shader compile error or test assertion included in the notification body — not just "build failed, see logs."
The scenario where this pays off: a programmer pushes a C++ change at 5pm that causes a PS5 shader compile failure that wouldn't surface on PC. Without the feedback loop, that failure sits undetected until the morning build review. With it, the programmer gets a Slack message at 5:15pm with the specific compiler error, fixes it before end of day, and the morning build starts from a clean state.
Workspace Canonicalization: The Farm Sync Problem
The most common build farm misconfiguration we see: the farm agent maintains a persistent workspace and runs incremental syncs before each build. Incremental syncs are faster than full syncs — but they accumulate drift. If a previous build left uncommitted generated files in the workspace, or a sync failed midway and left the workspace in a partial state, subsequent builds run against a workspace that looks synced but isn't.
The safer pattern is to treat each build as requiring a verified workspace state. Before running UAT, the build agent should:
# 1. Sync to the exact commit hash from the webhook payload
diversion sync --branch main --commit a8f3d219
# 2. Verify workspace state matches expected commit
diversion status --verify-clean
# 3. If verify fails, perform a full clean sync and log the anomaly
# Then proceed to UAT
The clean sync fallback adds time to any build where workspace drift is detected, but it prevents the much more expensive scenario: a build that passes because it ran against a workspace with previously-generated files that masked a real content failure.
We're not saying incremental syncs are wrong for every case — on a large depot, always doing full syncs is expensive. The key is that the incremental sync must be verifiable. Your build agent should be able to assert "this workspace is exactly commit hash X" before starting a build, and have a recovery path when it can't.
Monitoring the Integration
Build farm integrations tend to degrade silently. A webhook endpoint that starts returning 502s gets retried a few times and then the events get dropped — builds stop triggering, nobody notices for two days because the build farm was "running builds normally" (the manually-triggered ones). Build result status annotations stop posting because the API key expired. These are real operational failures that result in invisible code quality regressions.
Monitor the integration at the edges: alert on webhook delivery failures, alert if a branch that normally triggers builds hasn't triggered one in more than N hours during active-push windows, and verify that build status annotations are being posted to commits. The integration is only as good as the monitoring that tells you when it's broken.