Last Week in Unreal: The Cook Never Cached (September 7 to September 13, 2026)

Unreal Engine Weekly: The Cook Never Cached
Week of September 7 to September 13, 2026 | 727 commits on ue6-main, 127 on ue5-main | Branch: ue6-main
Overview
Two small commits this week explain why your incremental cooks have been doing more work than they should. A class default object that mints a GUID in its constructor, and a component that writes to itself while being serialized, both made native class hashes differ between identical cooks, so dependent blueprints never cached and recooked every time. Neither commit is large. Both are the kind of thing you cannot find from the outside, because the symptom is a cook that is merely slower than it should be, not a cook that fails.
Around that, the week’s other through-line is the editor growing a tool surface built for a non-human caller. Epic shipped a native file-operations plugin, a plugin that reads your Codex and Claude configuration directories, an isolated undo buffer for tool calls, and a fix for a Model Context Protocol server that reported a live endpoint it did not have. On the build side, UnrealBuildAccelerator’s Linux port finally got the compressed-intermediates path that made the Windows path cheap. At 727 commits the week sits at the low end of this branch’s usual band, which is week-to-week variance rather than a slowdown.
Development Trends
The cook and cache determinism thread is now three months long
Pattern: every week since early August has carried at least one fix for a cache key that depended on something that is not an input. Shader job cache keys dropping at growth boundaries in early August, random numbers baked into packages the week after, cook metadata that did not match its own bytes at the start of September, and this week two separate sources of native class hash instability (25599f8a, 8c37fb4f) plus compile-relevant texture state that was missing from material shadermap keys (db1878a8).
Interpretation: this is not a bug cluster, it is a campaign, and the direction is consistent. Epic is making the cook reproducible by removing hidden inputs one at a time. Each fix carries a one-time invalidation cost and a permanent reduction in redundant work afterward. The pattern also tells you where to look in your own pipeline: the expensive cache bugs are not the ones that produce wrong output, they are the ones that produce correct output too often.
Who cares: build and CI engineers, anyone running incremental cooks on a farm.
Signal strength: Strong.
Agent tooling stopped being a plugin and started being infrastructure
Pattern: in-editor agent tooling has appeared in this report since mid-July, first as a toolset and crash-hardening audit, then as a background tool-call mechanism, then as reflection APIs reshaped around token cost. This week it arrives with a permission model with a restricted mode (f1f0470e), an isolated undo buffer so agent edits stay out of the user’s undo queue (6c091f40), a hardened Model Context Protocol transport (b11e879d), a catalog-caching proxy (8a6f9e96), and modal-popup suppression during tool calls (2ff22fa2).
Interpretation: the failure modes being fixed are the ones that only matter when a machine is driving. A human notices a modal dialog and dismisses it. An agent hangs on it. The presence of a restricted mode that hides read operations, not just writes, says Epic expects this surface to run against projects where the caller is not fully trusted.
Who cares: tools programmers, pipeline and automation teams, anyone evaluating model-driven editor automation.
Signal strength: Strong.
UnrealBuildAccelerator’s Linux port caught up, and its author concentration loosened
Pattern: we have flagged for a month that UBA’s commits came almost entirely from one author. This week it is 32 of 40, with a second author at 7. Last week it was 38 of 39. The work itself moved to Linux: compressed intermediate files (26ad0bae), a native visualizer on both Wayland and X11 (bf02cca5, 874ab3f1), and a fix for Ctrl-C not reaching UnrealBuildTool at all on Linux (ebf19945).
Interpretation: a second name in the commit log is the first movement on this in five weeks of watching it, and it is worth reading as capacity rather than as a handover, because the primary author’s volume did not drop. The Linux focus is the more consequential half. Compressed intermediates are the mechanism that made UBA’s remote-compile path cheap on Windows, and cloud build farms for UE studios are usually Linux.
Who cares: build and CI engineers, studios running distributed compiles on Linux agents.
Signal strength: Moderate.
The renderer decomposition started removing console variables, not just code
Pattern: the SceneData migration has produced a cluster every week since mid-July. The character keeps escalating. Converting call sites gave way to deleting the old ones at the start of September, and this week legacy primitive HZB occlusion culling was removed along with the r.HZBOcclusion console variable (a89416ed), while GetLumenSceneData moved out of FScene entirely (e9c15aa6).
Interpretation: a removed function is a compile error you fix in an afternoon. A removed console variable is a shipped configuration that silently stops existing, and anyone carrying it in a device profile or a studio default finds out at runtime. This is the point in a migration where the merge cost stops being mechanical.
Who cares: graphics programmers maintaining custom renderer code, anyone planning a UE6 merge for an engine fork.
Signal strength: Strong.
TL;DR
- Two commits fixed native class hashes that changed between identical cooks, so dependent blueprints stopped caching and recooked forever (
25599f8a,8c37fb4f). Expect a one-time invalidation of every dependent blueprint on the first cook after you take these, then less work permanently. -SkipOnlyEditorOnly=falseis no longer supported and now activates a narrower reachability mode (7d0a26b5). Packages that were only ever pulled into your cook incidentally will stop being included. If you rely on that, declare the dependency.- UnrealBuildAccelerator got compressed intermediate files on Linux (
26ad0bae), and Ctrl-C now actually cancels UnrealBuildTool there (ebf19945). - Epic shipped an agent-facing tool surface into the editor, including a plugin that discovers skills, agents, and
AGENTS.mdorCLAUDE.mdfrom configured Codex and Claude directories (dc21275d). - The renderer removed the
r.HZBOcclusionconsole variable along with legacy primitive HZB occlusion culling (a89416ed). - A Verse codegen fast path took script deserialization on a 5000-message repro from 15.5 minutes to 1.4 seconds (
ac696db9). - If you ship on 5.8: a per-bone blend setup fix that only affected cooked builds, a Sequencer duplication bug that overwrote the original’s anim sequence assets on disk, and a Vulkan regression that rendered large TMV plates black. All three are cherry-pick candidates, and they are covered with their commit references in the 5.8 section below.
Highlights
The cook has been recooking blueprints that never changed
What changed
Two independent sources of instability in the cook’s native class hash were fixed. HashNativeClass stringifies a class and its class default object to detect when a native class has changed. A default object holding a member whose constructor calls FGuid::NewGuid() hashes differently in every process, so the class reported as changed on every cook and its dependent blueprints never cached. The fix adds an opt-in filter flag for members already tagged as unstable at initialization, and tags three members that were missing it (25599f8a). Separately, USplineComponent::Serialize assigned LastAuthority whenever the archive reported saving, and the dependency hasher stringifies through an archive that reports saving, so hashing a class whose default object holds a spline mutated that object. Whether the stamped value landed in the hashed text depended on what else had serialized the component earlier in the process, which made the hash differ between identical incremental cooks (8c37fb4f).
Why this is important
These are the expensive kind of cache bug, because the output is correct. Nothing fails, nothing looks wrong, and the only symptom is that incremental cooks do full-cook work on blueprints nothing touched. Both commits note that the first cook after the change is a one-time invalidation of every dependent blueprint, so budget for one slow cook and measure the steady state after it.
Who should care
Build and CI engineers, anyone running incremental cooks on a farm, studios where cook time is a scheduling constraint.
Urgency
Act now.
The cooker’s reachability rules changed, and packages will cook out
What changed
-SkipOnlyEditorOnly=false is deprecated, warns when set, and now activates -Cook.RequiredReachability=Editor rather than the full behavior it used to provide. Two consequences are stated in the commit. Packages incidentally loaded while loading and saving a referenced package are no longer included in the cook unless they are declared in AssetRegistry dependencies or actually used in the cooked package. And in MultiPlatform cooking, packages required on one platform are no longer automatically added to the other platforms (7d0a26b5).
Why this is important
Incidental-load reachability is the sort of thing a project depends on without anyone deciding to. If an asset has been arriving in your cooked build because something else happened to load it during a save, it stops arriving, and the failure shows up at runtime as a missing reference rather than at cook time as an error. The MultiPlatform half has the same shape across platforms.
Who should care
Build engineers, anyone cooking for more than one platform in a single invocation, teams with large legacy content sets.
Urgency
Act now.
UnrealBuildAccelerator’s Linux port stops paying full freight
What changed
Compressed intermediate files landed on Linux, covering .obj, .o, .pch, and .gch (26ad0bae). The implementation hands the compiler process a real file descriptor to a session-owned file mapping, so reads, writes, and seeks go to the kernel rather than through Windows-style emulated file semantics over a shared-memory view. It also adds a clang linker dependency crawler so object files decompress in parallel as the linker opens them rather than serially. The commit states it is not enabled on macOS, because darwin cannot reopen a shared-memory object and file position would end up shared between processes.
The tail of bugs that followed is instructive, and all of it landed in the same week: a cross-device move of a streamed write truncating files to zero bytes, because the copy fallback used the creation size that compressed writers set to zero (c84a58fc); the copy fallback not preserving timestamps, so deferred cache creation re-read and re-hashed the file (bad3d287); and storage not reporting a size for a prefetched cache file, so the detour fell back to a directory-table size produced on a different machine (652609f3, a79c60b6).
Separately, Ctrl-C had stopped cancelling UnrealBuildTool on Linux. SetupOrphanReaper() called setpgid(0,0) without a following tcsetpgrp(), so terminal-generated SIGINT never reached the process. It sits in a startup path UnrealBuildTool calls unconditionally, so the executor you were using did not matter (ebf19945).
Why this is important
Compressed intermediates are the mechanism that made UBA’s remote-compile path cheap on network and disk on Windows. Linux agents have been paying full-size transfers for every object file and precompiled header. For studios whose farms are Linux cloud runners, which is most of them, this changes the transfer economics of distributed compilation. The accompanying bug tail is a fair warning that it arrived by being shipped and exercised.
Who should care
Build engineers, CI and infrastructure teams, anyone running distributed UE compiles on Linux agents.
Urgency
Act now if you are on Linux agents, and benchmark before and after rather than assuming the win.
Epic shipped an agent-facing tool surface into the editor
What changed
A cluster of work made editor tooling safe and available for a non-human caller. FileSystemToolset is a new plugin exposing nine native file operations, including reading images up to 64 MiB, listing directories, writing and editing text, deleting files, and downloading HTTPS URLs. It hides all nine tools, reads included, when a registered permission provider reports restricted mode, because native paths bypass asset permission rules (f1f0470e). Follow-ups raised the download ceiling (24b5b652), fixed directory listings for reserved Windows filenames (1b05afef), and fixed a crash where probing DNS names through ISocketSubsystem::GetAddressFromString translated whatever socket error happened to be pending and hit a check on Windows (814fef49).
AgentPluginToolset is also new, and it discovers Codex and Claude plugin manifests, direct child skills, agents, and AGENTS.md or CLAUDE.md from the current tool working directory, with deterministic capability IDs and rejection of names containing the characters used to delimit capability references (dc21275d). The registry underneath gained a real execution model: tool calls now receive a context carrying a cancellation token and an isolated undo buffer, installed for the duration of the call so agent edits stay out of the user’s undo queue (6c091f40, 4b8bace5).
Two fixes show the shape of the risk. FModelContextProtocolServer::Start requested its HTTP router before listeners started, so the server reported a configured endpoint that did not exist, and a second editor could connect to the first through the shared port (b11e879d). And because a comment box is a graph node that stores its label in the same field a node note uses, set_node_note overwrote comment box labels and reported success, which means an agent tidying notes across a graph would have quietly rewritten every comment box it touched (66ef7da5).
Why this is important
The details give away how seriously this is being taken. A restricted mode that hides reads rather than only writes, an undo buffer that isolates a caller’s edits, modal-popup suppression during tool calls (2ff22fa2), and editor screenshots forced to opaque alpha because the images are handed to a vision model that composites against an unknown background (f35c9967). These are not the concerns of a prototype. If you are planning editor automation, this registry is the surface to build against rather than a bespoke bridge.
Who should care
Tools programmers, pipeline and automation teams, anyone evaluating model-driven editor automation.
Urgency
Track.
The renderer removed a console variable, not just code
What changed
Legacy primitive HZB occlusion culling was removed along with the r.HZBOcclusion console variable, preserving hardware queries, mobile feedback, and feature-specific HZB consumers (a89416ed). GetLumenSceneData moved out of FScene and now returns a reference, so a Lumen-specific extension is no longer included across the whole renderer (e9c15aa6). Deprecated code was removed from Renderer and RenderCore (0ad1a9c8, 65641439).
The migration underneath continued at pace. Velocity moved onto scene data and dirty tracking, storing a persistent primitive ID rather than a scene-info pointer, with the registration set becoming a bool data array so freed IDs reset automatically and recycled IDs cannot inherit a stale bit (657a7a7d). Capsule shadows did the same, with the indirect-shadow gather reading proxies and bounds through read views (a589535d). A parallel iteration primitive landed for the whole migration to use (6d2cea0a). Hardware ray tracing is being re-homed the same way, moving primitive registration into a ray tracing scene extension (f76c2e3a) and splitting base and decal instances into separate cached entries for about 0.1ms of CPU time in the MegaLights demo (5ee6b5a9).
Why this is important
A removed function is a compile error. A removed console variable is a shipped configuration that stops existing, and a device profile or studio default carrying r.HZBOcclusion now sets nothing. This is where the merge cost of the SceneData work stops being mechanical, because the question is no longer whether your code compiles but whether it assumed an invariant that is gone.
Who should care
Graphics programmers maintaining custom renderer code, engine-fork maintainers, technical directors planning a UE6 merge.
Urgency
Act now if you maintain a renderer fork.
Verse load times: 15.5 minutes to 1.4 seconds
What changed
Islands with many <localizes> declarations hung on load. Each message initializer emitted roughly 19 property operands plus 5 temporary properties into the shared initialization function, and every operand deserialized through a field-path resolve that linearly scans the owner’s field list, making the whole thing quadratic in message count. A codegen fast path now emits simple message literals as a single MakeMessageLiteral call with 3 operands and 1 temporary. On a 5000-message repro, script deserialization went from 15.5 minutes to 1.4 seconds (ac696db9). The commit notes that other functions and types can hit the same pattern, and that the new VM does not, calling the VM swap the long-term fix.
Alongside it, a Verse interface-field memory corruption was fixed. The compiler’s check for which class controls an interface field’s C++ declaration succeeded for the first class to override the field without considering that a superclass may already have defined it, producing two copies of the field, with the subclass’s binding overwriting the superclass property’s offset (4b146290).
Why this is important
The number is the story. A quadratic in a load path is the kind of thing that is invisible at small content scale and fatal at large content scale, and the repro size where it becomes fatal is well inside what a shipping island can reach. The interface-field bug is the more dangerous of the two in a quieter way, since memory corruption from a compiler-side ownership check does not present as a Verse problem.
Who should care
Teams shipping Verse content at scale, anyone whose editor or cook load times have grown non-linearly with content.
Urgency
Act now if load times are a problem for you.
The Windows link step is in flux
What changed
Windows distributed ThinLTO backend codegen was corrected to match lld’s default optimization level for all backends and to restore function and data sections. The measured result on a Win64 Test build at the same head: the text section shrank from 525.0 MB to 440.4 MB, section hashes matched the in-process link, all 480,230 text functions matched in size, and backend warning lines went from 19,698 to zero (e2d40c29).
Rad Game Tools’ linker is being stabilized in the same window, with four binary drops in three days and a backout between them. One drop shipped a build carrying two patches, numbered 934 and 935, that the later drops do not (cfae9390), was backed out (6d00de1b), and the three later drops all list a different base and do not carry those two patches, so that specific build did not come back this week. Separately, radobj and radlink are now explicitly disabled when profile-guided optimization is being used, in both the optimize and profile directions (a3a141f6, c89635df).
ASan needed toolchain work on both platforms: forcing ASan libraries to the start of the library list and import table, because atexit table allocations were using unshadowed memory (f27dd11a), and fixing Linux server ASan runtime loading where an architecture suffix added during copy broke the dependency name baked in at link time (0920739a).
Why this is important
If you ship profile-guided optimized Windows builds, the radlink exclusion is a live constraint rather than a note. The ThinLTO numbers are worth having as a baseline: a correctly configured distributed backend should match the in-process link byte for byte, and if yours does not, the warning count is the tell.
Who should care
Build engineers on Windows, console and platform teams, anyone using PGO or sanitizer builds.
Urgency
Act now for PGO users. Track otherwise.
Ambient Lighting Volumes leave mobile
What changed
Diffuse from capture was extended to the desktop deferred Substrate non-Lumen path, with the renames that go with leaving mobile behind: bMobileDiffuseFromCapture became bDiffuseFromCapture, and r.Mobile.DiffuseFromCapture became r.ReflectionCapture.DiffuseFromCapture (8aca08e0). Tile-culled ALV sampling was added for Lumen screen probes and translucency lighting volume froxels, and the console variable dropped its experimental infix, from r.AmbientLightingVolume.Experimental.EnableForLumen to r.AmbientLightingVolume.EnableForLumen. Measured on PC on a level carrying many volumes, the Lumen probe gather went from 1.9ms without the acceleration structure to a stable 1.5ms with it, matching the cost of hiding all the volumes. World-space probes keep the brute-force path because they can sit outside the view frustum (7b246bea).
Why this is important
Dropping an experimental infix from a console variable name and removing a mobile prefix from a feature flag is a rename, not a documented status change, and it should not be read as a graduation. It is worth noting anyway, because a desktop consumer, a Lumen integration behind a non-experimental variable, and an acceleration structure arriving together is the shape a system takes in the weeks before a default changes.
Who should care
Lighting artists, graphics programmers, mobile and cross-platform titles.
Urgency
Track.
The CI flakiness fixes are worth less than their diagnoses
What changed
A run of Gauntlet and Horde fixes this week read as a postmortem series. Clearing stale artifacts between reservation-block steps deleted the entire user directory, throwing away the editor’s asset registry cache and pip environment, so every step after the first spent about three minutes re-gathering 22,000 assets and reinstalling dependencies before dispatching a single test, which was long enough for an idle console to be PLM-constrained and the run killed with zero tests reported (ca638a0f). The Android transfer budget was widened after measurement against the slowest device in the pool, and more usefully, a transfer timeout now raises a specific exception rather than a generic device exception that pulled a healthy handset out of the shared pool for every other job (b852b661).
The Horde ref index is eventually consistent and ref reads are not retried internally, which is why artifact retrieval intermittently returned not-found for artifacts that existed while jobs immediately before and after succeeded (0094ee23). A staged CLI binary update notes that the new binaries alone would have made macOS clones exit with an argument-parse error, because the new version removed a flag the caller still defaulted on for macOS (d9c0a9d4). Two more are the same class of bug from opposite ends: an audio device garbage collection race hitting about a third of scheduled runs in one test group (7ec88951), and a use-after-free where a screenshot taker only unbound from a process-global delegate if a game viewport client happened to exist (2f4cefc3).
Why this is important
None of these are Unreal-specific. Eventually consistent reads without retries, a cleanup step that deletes a warm cache, an error type too generic to keep a healthy machine in the pool, and a delegate unbind behind a conditional are all failure modes that exist in any device-based automation. The fixes matter to Epic. The diagnoses are the part worth stealing.
Who should care
Build and QA infrastructure, anyone running automated device testing.
Urgency
Track.
Worth Tracking
Something called Ness is being built in Runtime/Ness, and it is reaching into CoreUObject. Twenty-one commits this week touch NessCore, NessUObject, and a NessEngine plugin: property bindings split per container kind, packets, load plans, diffing, roundtrip tests, an export map describing each export with a saved index and state, and hooks landing inside CoreUObject itself. One crash fix around map property bindings in Lyra reached the high-interest tier (5d187f0f), and the rest sat below it. The commit messages do not explain what it is for, and we are not going to guess. What is worth noting is the shape: a new serialization and binding layer, with its own module split, arriving alongside changes to the default save path. A new layer under CoreUObject is not a plugin experiment, and it is the sort of thing that is obvious in hindsight three releases later. We will keep counting it.
AutoRTFM is becoming per-module policy rather than one global switch. EpicClang gained a flag taking a mapping file that assigns default modes to directory paths, and an unmapped file path now raises a diagnostic rather than defaulting to enabled (3e96f550). UnrealBuildTool gained a matching ModuleRules.AutoRTFM property with the legacy flags kept as facades, explicitly changing no build files yet and not yet passing the new compiler flag (0d034f4c). The diagnostic on unmapped paths is the part to watch, because it means Epic now expects every file in the tree to have a declared mode. If you maintain out-of-tree modules, the migration commit that 0d034f4c promises is the one that will reach your build files.
What We Ignored
This week we ignored roughly 550 commits: localization automation, binary manifest drops for internal facial and PCG content pipelines, test-only fixes, XML documentation comments, editor UI polish, and the usual run of small cleanups. There were 15 commits tagged [Backout], which is inside the normal band for this branch and not a story on its own. Of them, the ones worth knowing about are covered above or did reland in-window. Two did not come back this week: USD runtime memory overloads (e43099c2, backed out by 84d4e02e) and a Composure compile fix (a929b1df, backed out by fd799bbc).
Build is the top subsystem at 97 commits, followed by editor and core at 61 each and rendering at 59. Of 727 commits, 160 worth reading.
For teams shipping on 5.8
ue5-main saw 127 commits this week with no backouts at all, and it is not pure maintenance. Most of the volume is Control Rig, RigVM, Sequencer, and Movie Render Graph authoring polish, but several fixes are worth a cherry-pick if you ship on 5.8 today.
- A per-bone blend regression that only affects cooked builds.
InitializePerBoneBlendhad been moved inside aWITH_EDITORblock, so per-bone blend setup ran in editor builds and silently did nothing in a cooked game (55537993). This is the most consequential fix on the branch this week, and it will not reproduce in PIE. - A Sequencer duplication bug that overwrote assets on disk. Duplicating a level sequence left the copy sharing the original’s link items, so saving the duplicate re-exported over the original’s linked anim sequences and stole their back-links. Duplicating an anim sequence copied its back-link untouched, leaving two anims claiming the same binding. The fix strips links from the duplicate by default. Note the intentional behavior change: a duplicated sequence no longer drives the original’s anims, so anyone relying on that sharing has to relink (
0c2b200c). - A Vulkan regression that rendered large TMV plates black. The intermediate target hint gated a reserved-resource flag on a global that only says the RHI can create the virtual resource. Vulkan reports true, but only D3D12 has a sparse output backend registered, so the texture got address space and never a committed tile. Only plates whose mip 0 exceeds the sparse-output threshold requested the flag, which is why HD and 4K kept working (
4157725d). - Two Control Rig bugs that produce wrong data rather than wrong UI. Applying multiple poses collapsed the hierarchy to the origin when every pose weight fell below relevance, because the empty influence list fell through to the branch that widens selection to every bone and wrote identity transforms into all of them, with a stale cache keeping it doing so on later frames (
398093ba). Separately, baking animation onto a control rig lost timecode attributes on both bake paths (190216d6). - Animation runtime crashes. A rig hierarchy could not grow past four megabytes, which an AnimSequence bake writing thousands of curve controls hit (
ea26c7c5); an unresolved function table was indexed with a bytecode callable index (b9abdf96); and a pose adapter asserted when a Control Rig recompiled while it was ticking (318396f9). - Three compile breaks that only show up on build farms. A missing editor guard that broke the non-unity build (
c3806dae), a mismatched guard in a dynamic property test (978eacc6), and a Composure compile failure from a merge (4f3ecf85). The first matters if your farm builds non-unity for warning cleanliness. - Deprecations, including one naming 5.9. Composure deprecates two members in 5.9 while retaining compatibility for now (
5f2f217f), and a MovieScene decoration accessor is deprecated in favor of a new one as decorations move to instanced structs (9e6c2d04). - A custom version bump on a maintenance branch. The Anim Sequence Transform Provider gained per-sequence and per-pose GPU instance bounds with readback and a runtime sequence allocation API, and it adds an entry to the stream object versions (
1be8db07). Worth reading carefully before merging if you carry local engine changes.
Closing
The two commits that mattered most this week are four files between them, and neither one fails anything. They just made the cook do work it had already done. That is the class of problem worth building a habit around: the cache that is always correct and never warm costs you more over a year than the bug that breaks the build, because the bug gets fixed the day it lands.
Commit SHAs are cited inline, in parentheses, next to each claim. They reference the ue6-main branch of EpicGames/UnrealEngine unless the claim is in the “For teams shipping on 5.8” section, which references ue5-main. Many of the highest-interest entries resolve to Commit.gitdeps.xml binary-manifest blobs: the commit message is real, but the code lives in Epic’s internal binaries, which we cannot see.
Never miss a ue5-main update.
Get them delivered straight to your inbox.


