# Bundled PSO Spc cache fails to load during cook and prevents packaging with it

**URL:** <https://forums.unrealengine.com/t/bundled-pso-spc-cache-fails-to-load-during-cook-and-prevents-packaging-with-it/2741454>\
**Category:** Issues and Bug Reporting (Unreal Engine)\
**Tags:** unreal-engine, open\
**Created:** [August 5, 2026, 4:01pm UTC](https://forums.unrealengine.com/t/bundled-pso-spc-cache-fails-to-load-during-cook-and-prevents-packaging-with-it/2741454 "2026-08-05T16:01:50Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![1aydan](https://d3kjluh73b9h9o.cloudfront.net/original/4X/2/8/5/28502927f1d97ba1051b3b47ccfe17233bf41ef0.png) [@1aydan](https://forums.unrealengine.com/u/1aydan)\
**Post date:** [August 5, 2026, 4:01pm UTC](https://forums.unrealengine.com/t/bundled-pso-spc-cache-fails-to-load-during-cook-and-prevents-packaging-with-it/2741454/1 "2026-08-05T16:01:50Z")

</div>

### Summary

Cooking fails with an assert while building the bundled PSO spc. The .spc file written by the “expand” step cannot be read back by the “build” step in the same engine build, so the cook dies at the very end with no output.

This occurs in engine source release and main epic engine release below is the revision the change was from.

(d3048e58e291 / CL 52102159, 2026-03-24)

### What Type of Bug are you experiencing?

Foundation (C++ Tools, Profiling, & Pipeline)

### Steps to Reproduce

1. On 5.8, use a project with the shader library and stable shader keys enabled.
2. Record a PSO cache from a run, so you have a .rec.upipelinecache plus the .shk stable key files.
3. Run the expand step to produce a .spc file. This succeeds:  
UnrealEditor-Cmd.exe .uproject -run=ShaderPipelineCacheTools expand “\<…\>/_.rec.upipelinecache" "\<…\>/_.shk” “\_PCD3D\_SM6.spc”
4. Put the .spc in /Build/Windows/PipelineCaches/
5. Cook or package for Windows. It crashes at the end of the cook.

### Expected Result

The .spc file written by the expand step loads correctly in the build step, and the cook finishes and produces a .stable.upipelinecache.  
A file saved by the same engine build should always be readable by the same build.

### Observed Result

The build step asserts and the cook fails with ExitCode=3.

### Affects Versions

5.8

### Platform(s)

Windows

### Upload an image

 ![image.png](https://d3kjluh73b9h9o.cloudfront.net/original/4X/6/e/2/6e274e1f7538ed82ce84a17c28f22b4ef69f3801.png)

### For crash reports, include your callstack

Assertion failed: PermDescriptor.ActivePerSlot[SF\_Compute] [File:…\Engine\Source\Runtime\RenderCore\Private\PipelineCacheUtilities.cpp] [Line: 205]  
UnrealEditor-RenderCore.dll!UE::PipelineCacheUtilities::Private::SanityCheckActiveSlots() [PipelineCacheUtilities.cpp:205]  
UnrealEditor-RenderCore.dll!UE::PipelineCacheUtilities::LoadStablePipelineCacheFile() [PipelineCacheUtilities.cpp:804]  
UnrealEditor-UnrealEd.dll!BuildPSOSC() [ShaderPipelineCacheToolsCommandlet.cpp:2600]  
UnrealEditor-UnrealEd.dll!UShaderPipelineCacheToolsCommandlet::StaticMain() [ShaderPipelineCacheToolsCommandlet.cpp:2910]  
UnrealEditor-UnrealEd.dll!UCookOnTheFlyServer::CreatePipelineCache() [ShaderLibraryCooking.cpp:536]  
UnrealEditor-UnrealEd.dll!UCookOnTheFlyServer::CookByTheBookFinishedInternal() [CookOnTheFlyServer.cpp:9799]

### Additional Notes

FIX WE ARE RUNNING LOCALLY (verified on 5.8.1-release)

File: Engine/Source/Runtime/RenderCore/Private/PipelineCacheUtilities.cpp

Line 598, in SaveStablePipelineCacheFile():  
(-) WriteVarUIntToArchive(MemWriter, NewPSO.UsageMask);  
(+) WriteVarUIntToArchive(PlainMemWriter, NewPSO.UsageMask);

Line 799, in LoadStablePipelineCacheFile():  
(-) OriginalPSOs[PermutationGroupIdx].UsageMask = ReadVarUIntFromArchive(MemReader);  
(+) OriginalPSOs[PermutationGroupIdx].UsageMask = ReadVarUIntFromArchive(PlainMemReader);

(see image for current local fix)

---

<div class="post-metadata">

**Author:** ![Fri\_Me\_Dev](https://d3kjluh73b9h9o.cloudfront.net/original/4X/2/8/5/28502927f1d97ba1051b3b47ccfe17233bf41ef0.png) [@Fri\_Me\_Dev](https://forums.unrealengine.com/u/Fri_Me_Dev)\
**Post date:** [August 5, 2026, 7:36pm UTC](https://forums.unrealengine.com/t/bundled-pso-spc-cache-fails-to-load-during-cook-and-prevents-packaging-with-it/2741454/2 "2026-08-05T19:36:21Z")

</div>

Confirming this on **5.8.1** (launcher build CL-56057345). So it is not fixed in the hotfix. Different project, Android/Vulkan (`SF_VULKAN_ES31_ANDROID`), packaging for standalone XR headsets.

Two things that might help pin it down.

**1. It is not engine-version skew, and the write side is fine.** Our `.spc` was originally produced under 5.8.0, so we re-ran `expand` under 5.8.1 to have the engine write a fresh one. `expand` succeeds (exit 0, reads the older `.rec`/`.shk` without complaint). Feeding that brand-new file straight back into `build` crashes the same way, ~60 seconds after it was written. The defect is purely on read-back.

**2. The crash signature is unstable, which makes this easy to mis-triage.** Three different asserts, all the same corrupted stream tripping whichever guard comes first:

| Where | Signature |
| --- | --- |
| 5.8.0 | `checkNoEntry()` - `PipelineFileCache.cpp:1424` |
| 5.8.1 | `Trying to resize TArray to an invalid size of 4294967287` - `ContainerHelpers.cpp:8`, in `RenderCore` |
| this thread | `PermDescriptor.ActivePerSlot[SF_Compute]` - `PipelineCacheUtilities.cpp:205` |

---

<div class="post-metadata">

**Author:** ![HenrikoMagnifico](https://d3kjluh73b9h9o.cloudfront.net/original/4X/2/8/5/28502927f1d97ba1051b3b47ccfe17233bf41ef0.png) [@HenrikoMagnifico](https://forums.unrealengine.com/u/HenrikoMagnifico)\
**Post date:** [August 8, 2026, 8:46pm UTC](https://forums.unrealengine.com/t/bundled-pso-spc-cache-fails-to-load-during-cook-and-prevents-packaging-with-it/2741454/5 "2026-08-08T20:46:41Z")

</div>

Same issue here. Desperately needs to fix it, as we don’t want to ship new updates without bundled PSOs!

---

<div class="post-metadata">

**Author:** ![Crltoz](https://d3kjluh73b9h9o.cloudfront.net/original/4X/2/8/5/28502927f1d97ba1051b3b47ccfe17233bf41ef0.png) [@Crltoz](https://forums.unrealengine.com/u/Crltoz)\
**Post date:** [August 18, 2026, 3:50pm UTC](https://forums.unrealengine.com/t/bundled-pso-spc-cache-fails-to-load-during-cook-and-prevents-packaging-with-it/2741454/6 "2026-08-18T15:50:49Z")

</div>

I’ve the same assert at `PipelineCacheUtilities.cpp:205`. My config: Win64 | D3D12 | PCD3D\_SM6, 5.8.1 CL 56057345

I made a research looking for why _1aydan’s_ fix works.  
Two caveats up front: this is static analysis against the installed 5.8.1 source because I have not been able to build a patched engine to confirm it. And all line numbers below are from the 5.8.1 release, so they will differ on `ue5-main` branch.

### 1. File that identifies a shader hash by the size of the write

`PipelineCacheUtilities.cpp:137`

```cpp

/**
 * FShaderHash values are serialized like a binary stream, so assume every serialization of this size is a FShaderHash.
 */
virtual void Serialize(void* V, int64 Length) override
{
    if (Length == sizeof(FShaderHash))
    {
        // ...treat as a hash: read and write a deduplicated index

```

### 2. Size changed from 20 bytes to 8 between 5.7 and 5.8

| | 5.7 | 5.8 |
| --- | --- | --- |
| Type | `FSHAHash` \> `alignas(uint32) uint8 Hash[20]` (`Misc/SecureHash.h:225`) | `FShaderHash : FXxHash64` \> a single `uint64` (`Hash/ShaderHash.h:11-17`) |
| `sizeof` | **20** | **8** |

With 20 bytes, the heuristic was reliable because no stream wrote exactly that amount. Now, with 8 bytes, it causes collisions, as that is one of the most common chunk sizes.

### 3. Varints are chunked differently on the write and read paths

`Runtime/Core/Private/Serialization/VarInt.cpp`

```cpp

// write: one call

void WriteVarUIntToArchive(FArchive& Ar, uint64 Value)

{
    uint8 Buffer[9];
    const uint32 Size = WriteVarUInt(Value, Buffer);
    Ar.Serialize(Buffer, Size);
}

// read: two calls
uint64 ReadVarUIntFromArchive(FArchive& Ar)
{
    uint8 Buffer[9];
    Ar.Serialize(Buffer, 1);
    uint32 Size = MeasureVarUInt(Buffer);
    if (Size > 1)
        Ar.Serialize(Buffer + 1, Size - 1);
    return ReadVarUInt(Buffer, Size);
}

```

So for a value whose varint encoding is **9** bytes:

| | bytes passed to `Serialize()` | intercepted as a hash? |
| --- | --- | --- |
| write | `Serialize(Buffer, 9)` | no \> 9 != 8 |
| read | `Serialize(Buffer, 1)`, then `Serialize(Buffer + 1, 8)` | **yes** , on the second call |

9-byte varints represent the worst-case scenario in a specific sense: the initial byte is `0xFF` and contains no useful data bits, so the remaining 8 bytes constitute the raw `uint64` exactly `sizeof(FShaderHash)`.

There is a reverse case: a value whose _varint_ representation occupies **8** bytes (`2^49 <= v < 2^56`) fails in the opposite way, it is intercepted upon writing but read in raw form. Any value encoded in 7 bytes or fewer is symmetric and safe.

That asymmetry creates a lack of synchronization and explains why bypassing the proxy at those two call sites resolves the issue. I would add that the proxy already performs exactly this action internally: when it needs to write its own _varints_, it sends them directly to `InnerArchive` (lines 147, 180, and 184), precisely to avoid intercepting itself.

### 4. Why the assertion appears late and with different signatures

In the loading loop, `LoadActiveSlots` executes **first** (line 791) before the PSO is read (797) and before the _varint_ fields (799–800) with the check occurring on line 804. This means that a synchronization mismatch introduced while reading permutation group N does not cause a failure at that point, but rather when group N+1 reads its active slots.

Depending on the content of the misaligned bytes, this manifests as the `ActivePerSlot` assertion, a `checkNoEntry`, a `TArray` resizing failure, or the “impossible stable shader key index” `checkf` statement on line 290. This aligns with Fri\_Me\_Dev’s observation that the signature varies by version and platform: it is the same synchronization issue manifesting at different points.

### What I could not confirm

**Which field actually holds the excessive value?** I initially assumed it was `NewPso.UsageMask = uint64(-1)` (`ShaderPipelineCacheToolsCommandlet.cpp:2224`), but that is incorrect: `AddComputePSOs` is only called from `BuildPSOSC` at line 2679 that is, _after_ the call to `LoadStablePipelineCacheFile` at line 2575 (where the assertion failure occurs) so that value never makes it into the `.spc` file. The usage masks that _do_ end up in the file come from the recording, where the default value is 0 (`GameUsageMask` is 0 unless the title calls `SetGameUsageMaskWithComparison`).

Thus, the mechanism described explains how the file desynchronization can occur and why the fix affects those two lines, but I have not yet determined exactly which specific value triggers the issue. The remaining candidates are the other points where `varint` is called; the `static_cast<uint64>` of an index at lines 510/512 seems the most likely culprit. If a source build is available, logging the `Length` value and the calling field within that `Serialize` overload should make it possible to identify the specific instance in a single run.

### On the fix itself

The two-line change appears structurally correct but is limited in scope. The other _varint_ call sites in the same file (599/800, 602/807, 510/512, 277/289) are safe only because their values happen to be small not by design, and without any validation to guarantee it. Furthermore, the heuristic is not specific to _varints_: _any_ 8-byte raw data using `Serialize` within that file is interpreted as a _hash_ upon reading.

A robust solution would be to stop identifying _hashes_ based on the `Serialize` length and instead provide `FShaderHash` with an explicit serialization path; this way, future modifications to the type would not reintroduce the same vulnerability.

**### Things that didn’t work for me, in case this happens to anyone else.**

- **Re-recording the PSOs doesn’t help** : I tried with two separate recordings, and both failed.
- **Deleting derived data doesn’t solve it either:** I tried deleting the DDC, the Zen store, and the cooked output, but the same assertion failure occurs.

---

<div class="post-metadata">

**Author:** ![MattDalpe](https://d3kjluh73b9h9o.cloudfront.net/original/4X/2/8/5/28502927f1d97ba1051b3b47ccfe17233bf41ef0.png) [@MattDalpe](https://forums.unrealengine.com/u/MattDalpe)\
**Post date:** [August 25, 2026, 5:18pm UTC](https://forums.unrealengine.com/t/bundled-pso-spc-cache-fails-to-load-during-cook-and-prevents-packaging-with-it/2741454/8 "2026-08-25T17:18:33Z")

</div>

Seems to be fixed in 5.8.2

[Fix a misaligned read in the stable PSO cache serialization triggered… · EpicGames/UnrealEngine@2664b45](https://github.com/EpicGames/UnrealEngine/commit/2664b45c8556a90f894babc94699000201a9ce56)

---

<div class="post-metadata">

**Author:** ![Bug-Reporter](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/bug-reporter/32/997076_2.png) [@Bug-Reporter](https://forums.unrealengine.com/u/Bug-Reporter)\
**Post date:** [August 31, 2026, 9:00pm UTC](https://forums.unrealengine.com/t/bundled-pso-spc-cache-fails-to-load-during-cook-and-prevents-packaging-with-it/2741454/9 "2026-08-31T21:00:36Z")

</div>

UE-390728 has been ‘Closed’ as a duplicate of an existing known issue. Origin Issue: UE-386797

---

<div class="post-metadata">

**Author:** ![Bug-Reporter](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/bug-reporter/32/997076_2.png) [@Bug-Reporter](https://forums.unrealengine.com/u/Bug-Reporter)\
**Post date:** [August 31, 2026, 9:00pm UTC](https://forums.unrealengine.com/t/bundled-pso-spc-cache-fails-to-load-during-cook-and-prevents-packaging-with-it/2741454/10 "2026-08-31T21:00:36Z")

</div>

UE-386797 is ‘Closed’ as ‘Fixed’. The issue will be addressed in 5.8.2.
