bLabelAssetsInMyDirectory is set but cooks are pushing everything over to Chunk 0

We’ve had an issue for a while now where our pakchunks stopped splitting up, and we’ve just got the one pakchunk0.

We figured that we had something critical moved over to the start up packages, but after reducing the list by 75% with no changes, I feel like I need some help.

After some forensics, I’ve discovered that the change actually lines up with an update from 5.3 to 5.5 a year ago.

I’m hoping that someone can recognize if there was some significant change to packaging rules that I might not have noticed, or how I can better debug how the directory labelling applies (or is not being applied in my case).

Note: I can use type based Primary Asset Labels to split out into multiple chunks, but that organization doesn’t work well for us, and if I gave a chunk id to every primary asset type we have it also balloons Paks folder from ~20 GB to ~100 GB… And we essentially add at least one asset of each type every patch, so it wouldn’t reduce our patch sizes at all.

[Attachment Removed]

Steps to Reproduce

This started when we were already a large game with 3 main large world partition maps, and 5 smaller maps (some old school streaming)

We had been packaging into 15 paks leveraging directory labels at the time, and our update from 5.3 to 5.5 seems to have included some change that led to all these getting pushed to pak 0 only.

  • Have game cooking multiple populated maps
  • Put a Primary Asset Label in a folder above one of the populated maps and mark the Label Assets In My Directory as true
    • We also actually added the root map in the explicit assets array too.
    • We did this for every major map too actually.
  • Cook the build… For us, there’s just the single pakchunk0 in the results.

I added a log to UAssetManager::GetPrimaryAssetSetChunkIds that shows that even when inspecting the map file that is in the explicit assets array, it returns 0 entries in the OutChunkList.

[Attachment Removed]

Unfortunately I don’t have a good list of what changed between 5.3 and 5.5, so I think the best approach is to debug your current code rather than looking for what changed.

My first suspicion is that your Maps are setting the ChunkID to 0 themselves and are overridding the chunk assignment that comes from the PrimaryAssetLabel. Maps are themselves PrimaryAssets, and can specify the chunkid.

Put a breakpoint in UAssetManager::GetPackageChunkIds and run the CookCommandlet. GetPackageChunksIds is called at the end of cook. You can make a shorter cook by running with -cooksinglepackagenorefs -package=<MapName>. That will also reduce the number of calls to GetPackageChunkIds. Step through the calls until the PackageName is the map package you are cooking, or set a conditional breakpoint, and then step through the function.

What is the list of Managers returned for the map package?

If it is the map itself that is the manager, the PrimaryAssetId for it will be “Map:<LongPackageNameOfMap>”, where <LongPackageNameOfMap> is the name of your map. If that is the manager, then I think this will be an easy fix. Step through the call to GetPrimaryAssetSetChunkIds that is called and verify that the Map is returning ChunkId 0.

The fix if so:

Unlike PrimaryAssetLabels, a map’s chunk is not settable through properties on the map asset, you have to set it through ini. The example to follow is given in //UE5/Main/Samples/Games/ShooterGame/Config/DefaultGame.ini:

[/Script/Engine.AssetManagerSettings]
;The cook rules for ShooterGame maps are currently implemented using PrimaryAssetLabels, instead of using labels you could also use these rule overrides:
;+PrimaryAssetRules=(PrimaryAssetId="Map:/Game/Maps/Sanctuary",Rules=(Priority=-1,ChunkId=1,CookRule=Unknown))
;+PrimaryAssetRules=(PrimaryAssetId="Map:/Game/Maps/Highrise",Rules=(Priority=-1,ChunkId=2,CookRule=Unknown))
;+PrimaryAssetRules=(PrimaryAssetId="Map:/Game/Maps/ShooterEntry",Rules=(Priority=-1,ChunkId=0,CookRule=AlwaysCook))

Add an instance of PrimaryAssetRules for your Map, and set the desired ChunkId of each one.

[Attachment Removed]

The decision to treat Maps (UWorld) as primary assets is made during UAssetManager::ScanPrimaryAssetTypesFromConfig, where it reads Game.ini:[/Script/Engine.AssetManagerSettings]:PrimaryAssetTypesToScan. That setting has one array element for maps in Engine\Config\BaseEngine.ini:

+PrimaryAssetTypesToScan=(PrimaryAssetType="Map",AssetBaseClass=/Script/Engine.World,bHasBlueprintClasses=False,bIsEditorOnly=True,Directories=((Path="/Game/Maps")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown))

Do you delete that element or clear that array in your DefaultGame.ini?

Is the Map PrimaryAssetTypeToScan found during ScanPrimaryAssetTypesFromConfig?

Is ScanPrimaryAssetTypesFromConfig called before UpdateManagementDatabase is called?

[Attachment Removed]

Your version of the PrimaryAssetType=“Map” rule has bHasBlueprintClasses=True. You need to turn that off; that instructs AssetManager to search only for UBlueprint assets, and return the ones that have ParentClass=TargetClass, and it will therefore miss UWorld; that is probably why your maps are not finding the “Map” manager.

I forgot to consider the paths; the default rule for Maps (and your override of it) only looks in /Game/Maps folder (and subfolders); are your maps in /Game/Maps or a subfolder? If not, you will need to add the directory where your maps are (or a parent directory) to the list of directories in the rule.

[Attachment Removed]

We should keep following the trail of execution for the Map PrimaryAssetType. When we find the piece that is missing I expect that it will explain why the directory-based label is not working as well.

GetPackageManagers is still the first apparently failing piece, and it is failing despite the presence of the Map PrimaryAssetType.

GetPackageManagers looks up the list of managers for each asset by reading flags in the AssetRegistry that were set at beginning of cook during UpdateManagementDatabase in AssetManager.cpp. UpdateManagementDatabase finds the list of PrimaryAssets for each PrimaryAssetType, and does a graph search out over their package references to find all transitively referenced packages. It stores a flag for each one found that it is managed by the given PrimaryAsset. There is some complexity to handle also marking nevercook assets with the same PrimaryAssets, and there is some complexity to handle different priorities to decide which manager wins when multiple managers reference the same asset.

This is a nested for loop, over every PrimaryAssetType, and then over every PrimaryAsset found for that type, in UpdateManagementDatabase

for (const TPair<FName, TSharedRef<FPrimaryAssetTypeData>>& TypePair : AssetTypeMap)
{
	const FPrimaryAssetTypeData& TypeData = TypePair.Value.Get();
 
	for (const TPair<FName, FPrimaryAssetData>& NamePair : TypeData.GetAssets())
	{
		const FPrimaryAssetData& NameData = NamePair.Value;
		FPrimaryAssetId PrimaryAssetId(TypePair.Key, NamePair.Key);
 
		FPrimaryAssetRules Rules = GetPrimaryAssetRules(PrimaryAssetId);

Does your map show up in the inner loop? The LongPackageName of the map will be in the NamePair.Key variable.

Assuming it shows up, we will next need to look at the rest of the function, where it sends the packages directly referenced by that Map to an AssetRegistry function for transitive search; one of those packages is the Map package itself, which makes it seem likely that the map must not be reaching this far.

The map package itself is added to AssetPackagesReferenced on line 4039, every element of AssetPackagesReferenced is copied into either PriorityManagementMap or NoReferenceManagementMap on line 4073, and those maps are passed to the AssetRegistry’s graph search in calls to AssetRegistry.SetManageReferences on lines 4107 or 4113.

Do you have a subclass of UAssetManager that you are using on your project? For it to be used, it needs to be listed in your DefaultEngine.ini, in the setting Engine:[/Script/Engine.Engine]:AssetManagerClassName. If you have one set, what virtuals does it override? UpdateManagementDatabase and ShouldSetManager are the ones I know are relevant to this step of the calculation.

[Attachment Removed]

Yes, the PrimaryAssetLabel that you are using to label assets in its directory is supposed to show up in the same loop, and yes, all of the assets in its directory should be reported in the “Directory” bundle of the BundleMap found for it in CachedAssetBundles.

No, setting the PrimaryAssetLabel to always cook does not make a difference here. PrimaryAssets have three jobs, and can be configured to do some or none of those jobs: (1) Add assets to be cooked (2) Prevent assets from being cooked (3) Set the chunk of assets that were cooked. Setting the PrimaryAssetLabel to AlwaysCook affects (1), it does not impact (3).

Next thread to follow: The map’s LongPackageName is present in AssetPackagesReferenced; which ManagerMap does it get put into? One of the maps in PriorityManagementMap? Allow the function to continue down to the call to AssetRegistry.SetManageReferences for that map down below. Before SetManageReferences is called for it, set a breakpoint in your ShouldSetManager function. Turn off optimizations via UE_DISABLE_OPTIMIZATION at the top of your file to avoid possible inlining from the compiler that could cause your breakpoint to be missed.

You should turn off optimizations in Engine\Source\Runtime\Engine\Private\AssetManager.cpp and Engine\Source\Runtime\AssetRegistry\Private\AssetRegistry.cpp as well as your own AssetManager file, to make it easy to inspect data in the debugger.

Add instrumentation to your ShouldSetManager function:

	static FName LocalMapTypeName(TEXT("Map"));
	if (Manager.PrimaryAssetType.GetName() == LocalMapTypeName)
	{
		static volatile int HitBreakPoint = 0; ++HitBreakPoint;
	}

Put a breakpoint on the HitBreakPoint line.

Does ShouldSetManager get called with your map as Manager during that call to SetManageReferences? (Manager.PrimaryAssetType.Name == “Map”, Manager.ObjectName == “<YourMapName>”). Does it get called with Target.PackageName == <YourMapName> (called with it’s own name)? That should be the first call to ShouldSetManager that occurs with Manager.ObjectName == “<YourMapName>”. What value does the ShouldSetManager function return for that combination of Manager and Target? Up the callstack should be a lambda function from FAssetRegistryImpl::SetManageReferences, this will be the lambda named auto IterateFunction, but I don’t think the debugger shows that name. After it gets the EAssetSetManagerResult::Type Result from ShouldSetManager, does it call NodesToManage.Add(TargetNode, ManageProperties)?

Assuming it reaches that far, we can probably assume without need for verification that it calls ManagerNode->AddDependency with that manager and packagename, down below on line 8766. We should then add instrumentation at the end of UpdateManagementDatabase to verify that that dependency is present:

	FAssetIdentifier MapPackageAssetId(FName(TEXT("<YourMapLongPackageName>")), NAME_None);
	TArray<FAssetIdentifier> LocalReferencers;
	AssetRegistry.GetReferencers(MapPackageAssetId, LocalReferencers, UE::AssetRegistry::EDependencyCategory::Manage);
	UE_LOG(LogAssetManager, Display, TEXT("%s has %d referencers:"), *MapPackageAssetId.PackageName.ToString(), LocalReferencers.Num());
	for (FAssetIdentifier& ManageId : LocalReferencers)
	{
		UE_LOG(LogAssetManager, Display, TEXT("Referencer of %s: %s"), *MapPackageAssetId.PackageName.ToString(), *ManageId.ToString());
	}
	static volatile int HitBreakPoint = 0; ++HitBreakPoint;

I tested that on Lyra at head with <YourMapLongPackageName> == /TopDownArena/Maps/L_TopDown_LocalMultiplayer, and got this output:

[2026.08.05-12.51.10:711][  0]LogAssetManager: Display: /TopDownArena/Maps/L_TopDown_LocalMultiplayer has 2 referencers:
[2026.08.05-12.51.10:713][  0]LogAssetManager: Display: Referencer of /TopDownArena/Maps/L_TopDown_LocalMultiplayer: Map:/TopDownArena/Maps/L_TopDown_LocalMultiplayer
[2026.08.05-12.51.10:713][  0]LogAssetManager: Display: Referencer of /TopDownArena/Maps/L_TopDown_LocalMultiplayer: PrimaryAssetLabel:TopDownArena_Label

That is the data that is supposed to be read during GetPackageManagers.

Does it show any entries for your map at the end of UpdateManagementDatabase?

If so, add it as well to GetPackageManagers, which is called at end of cook. Does it still show the same entries at that point?

[Attachment Removed]

That’s a difference I wasn’t expecting, that your basic Item asset is a primaryAsset and somehow refers to the map, so that your map (and many other assets as well, presumably) has hundreds of managers. Having that many managers is not a behavior problem as far as I know, but since it’s unexpected it might be a performance problem somewhere due to some container or function that is expecting only a few managers per asset.

It’s a Nice to Have that you investigate the package dependency graph from some of your Items to your Map, and find the dependency that is out of place and is causing those Items to be able to refer to the map.

But that is unrelated to the question of why GetPackageManagers is returning empty on your assets that are supposed to be labeled by the Map or by your PrimaryAssetLabel.

I thought you said that GetPackageManager was returning 0 managers found for the map, but I looked above and see that you reported 0 managers for a different asset: A prop asset used by the map.

> LogAssetManager: Display: UAssetManager::GetPackageChunkIds - Kicking off /Game/Props/Decor/Dragon/<ANY_PROP_MESH> with already existing list sized 0 against 0 known managers

Change your diagnostics that we added to UpdateManagementDatabase looking at managers of the map into a diagnostic looking at one of those Prop Meshes.

Does ShouldSetManager ever get called with Target.PackageName == /Game/Props/Decor/Dragon/<THE_PROP_MESH> ?

If you pass in that packagename to the AssetRegistry.GetReferencers call we added to the end of UpdateManagementDatabase, does it find 0 manager referencers?

If it finds 0 manager referencers, we should start stepping through UpdateManagementDatabase and the data that comes before it to find out why.

Is the PropMesh used by an actor in the map?

Is the label you have, that has Label Assets in My Directory checked, present in or in a parent directory of /Game/Props/Decor/Dragon?

Your PrimaryAssetTypesToScanRule for PrimaryAssetLabel has a path I haven’t seen used before: the root path specifier by itself: “/”. When UAssetManager::ScanPathsForPrimaryAssets is called for PrimaryAssetType == “PrimaryAssetLabel”, does it find all of your labels and store them in AssetDataList in the call to SearchAssetRegistryPaths(AssetDataList, SearchRules)? I tested it locally on head code and found that it does work, if it’s the only element in the Paths list, but if any other config setting appends another Path to the list, the root “/” path will be ignored.

[Attachment Removed]

Yes, with chunkid==-1, the Items will not impact the chunk assignment, so we don’t need to worry about them now.

Yes, the root path would be more orthodox to specify as /Game, and if you want to search GameFeaturePlugins as well, you would add those as additional paths:

+PrimaryAssetTypesToScan=(PrimaryAssetType="PrimaryAssetLabel",AssetBaseClass="/Script/Engine.PrimaryAssetLabel",bHasBlueprintClasses=False,bIsEditorOnly=True,Directories=((Path="/Game"), (Path="/Plugin1"), (Path="/Plugin2")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown))

It’s possible attempting to use “/” fails in 5.5 and later due to a change in behavior in 5.5, but I don’t have any evidence to support that idea yet beyond my static analysis of the code in 5.5. that “/” in a list with multiple elements will be ignored; I haven’t looked at the 5.3 code.

No need to investigate that further either, if that’s the problem we’ll find it out when we see the results of your new tests (possibly after some further followup tests).

[Attachment Removed]

> Could there have been a gap there that we were unknowingly taking advantage of during this time?

It’s possible, but there’s another question we need to answer first: why is GetPackageManagers returning 0 managers.

Your original diagnostics printed out this message

LogAssetManager: Display: UAssetManager::GetPackageChunkIds - Kicking off /Game/Props/Decor/Dragon/<ANY_PROP_MESH> with already existing list sized 0 against 0 known managers

But your new diagnostics report 1534 manage referencers to /Game/Props/Decor/Dragon/<ANY_PROP_MESH>.

GetPackageManagers reads those manage referencers itself, and returns each one in the list of managers:

AssetManager.cpp:3655:

AssetRegistry.GetReferencers(PackageName, ReferencingPrimaryAssets, UE::AssetRegistry::EDependencyCategory::Manage);

Can you step through GetPackageManagers for the mesh and find out how it is returning an empty list?

[Attachment Removed]

Now we’re getting somewhere, if we have a non-zero number of managers. Maybe add a warning message in GetPackageChunkIds where GetPackageManagers finds 0 assets, so we can return to that problem and solve it separately.

But now we should move on to the next step of chunk assignment, the choosing of which manager to use that occurs in GetPrimaryAssetSetChunkIds.

Note that it ignores all elements with Rules.ChunkId == INDEX_NONE, that should eliminate all of your Items and therefore most of the managers.

Are there any managers in the list that do not have ChunkId == INDEX_NONE?

If so, what are their names? Do they have Rules.ChunkId == 0?

If not, then we have to go back to look at UpdateManagementDatabase and look at the PrimaryAssetLabel to find out why it (a) has Chunkid == INDEX_NONE or (b) is not setting itself as a Manage referencer of the packages in its directory.

[Attachment Removed]

One theory: the PrimaryAssetLabel is not adding itself to as a manager because the Items have a higher Priority score (higher integer values == higher priority), so they add themselves as the manager and prevent the PrimaryAssetLabel from registering itself because it has a lower Priority score, and the code that looks at priorities and decides who wins does not consider ChunkID at all, it only considers the Priority.

The code that decides that is the if block

	if (Flags & EAssetSetManagerFlags::TargetHasExistingManager)
	{
		// If target has a higher priority manager, never recurse and only set manager if direct
		if (Flags & EAssetSetManagerFlags::IsDirectSet)
		{
			return EAssetSetManagerResult::SetButDoNotRecurse;
		}
		else
		{
			return EAssetSetManagerResult::DoNotSet;
		}
	}

in UAssetManager::ShouldSetManager. Higher priority managers execute their search in an earlier, separate, graph search than lower priority managers.

That’s a theory, but it’s probably wrong and not causing the problem, because the assets labeled by a PrimaryAssetLabel should be marked as DirectSet from that PrimaryAssetLabel, and therefore get EAssetSetManagerResult::SetButDoNotRecurse even if there is a higher priority manager.

[Attachment Removed]

Roger, thanks for debugging this. I will fix it at head to interpret / as search the entire depot.

[Attachment Removed]

Fixed in 6.0 code: github commit 9c9dd2f3c34a3e585734e9d68daa4269379b9fc0 . We now handle ‘/’ specially to mean “Search All Paths”, it was failing before because our normalized directories have the final slash removed, so ‘/’ has to be handled as a special case.

While making the change I checked to see if there is a problem with / being at the beginning, end, or middle of the list, but I didn’t spot anything through static analysis or some test cases in my new version of the code. So I’m guessing that the previous misbehavior was a chaotic consequence of not treating / as a special case.

[Attachment Removed]

We use some other primary asset types, so the managers were pretty filled up with a bunch of data assets… With chunk id specified at -1. When I tried a single map (but not single package) previously, I had ~4000 entries in the managers but did not notice the map there… But potentially wasn’t looking closely enough.

I’ll try those steps. Sounds like a much better iteration loop.

I should be able to come back later today with the results.

Thanks!

[Attachment Removed]

Hmm maybe I already have the data I need? I am presently rebuilding after gathering some changes my teammates made, but I can look through my logs that I’ve augmented for the results of last week…

LogAssetManager: Display: UAssetManager::GetPackageChunkIds - Kicking off /Game/Maps/<OUR_MAP> with already existing list sized 0 against 0 known managers
LogAssetManager: Display: UAssetManager::GetPrimaryAssetSetChunkIds - ran to find highest chunk 0 amongst total list size 0

Where I added those two logs into AssetManager.cpp.

The first is right at the end of GetPackageChunkIds right before calling GetPrimaryAssetSetChunkIds

UE_LOG(LogAssetManager, Display, TEXT("UAssetManager::GetPackageChunkIds - Kicking off %s with already existing list sized %d against %d known managers"), *PackageName.ToString(), OutChunkList.Num(), Managers.Num());And the second is right before creating DependencyInfo in GetPrimaryAssetSetChunkIds.

UE_LOG(LogAssetManager, Display, TEXT("UAssetManager::GetPrimaryAssetSetChunkIds - ran to find highest chunk %d amongst total list size %d"), HighestChunk, OutChunkList.Num());

Which… if I’m interpreting your theory correctly… this is saying that there’s no appropriate manager for the map, and thus it’s pushing it to chunk 0? And then anything downstream from the map is coming back as 0?

… I am realizing now that while I picked the smallest map for my convenience, it is one of the few maps that doesn’t have a directory label next to it.

We have some universal meshes that should be getting affected by a directory style label that are being cooked… But not this map.

[Attachment Removed]

For an asset that is underneath a directory label, I can look at one of our Dragon themed props.

LogAssetManager: Display: UAssetManager::GetPackageChunkIds - Kicking off /Game/Props/Decor/Dragon/<ANY_PROP_MESH> with already existing list sized 0 against 0 known managers
LogAssetManager: Display: UAssetManager::GetPrimaryAssetSetChunkIds - ran to find highest chunk 0 amongst total list size 0

I was unable to tell at that time whether that was just saying there wasn’t an explicit rule against that asset, since it did seem like the label via directory pattern uses some separate bundling path that I have yet to understand.

[Attachment Removed]

It is modified, but not extensively…

-PrimaryAssetTypesToScan=(PrimaryAssetType="Map",AssetBaseClass=/Script/Engine.World,bHasBlueprintClasses=False,bIsEditorOnly=True,Directories=((Path="/Game/Maps")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown))
-PrimaryAssetTypesToScan=(PrimaryAssetType="PrimaryAssetLabel",AssetBaseClass=/Script/Engine.PrimaryAssetLabel,bHasBlueprintClasses=False,bIsEditorOnly=True,Directories=((Path="/Game")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown))
+PrimaryAssetTypesToScan=(PrimaryAssetType="Map",AssetBaseClass="/Script/Engine.World",bHasBlueprintClasses=True,bIsEditorOnly=True,Directories=((Path="/Game/Maps")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown))
+PrimaryAssetTypesToScan=(PrimaryAssetType="PrimaryAssetLabel",AssetBaseClass="/Script/Engine.PrimaryAssetLabel",bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown))

It looks like we have no changes to AssetManager.cpp beyond my log lines… And it looks like the Scan occurs roughly on load… I’ll need to adjust to confirm with a breakpoint… One moment (I made a separate code change; I couldn’t help myself)

[Attachment Removed]

Oh, that’s interesting. That’s been that way since… 2021, which the change in packaging chunks just started in 2025...

I do understand and believe that this could be problematic for our cooks, but I would be surprised if it’s the origin…

Edit: I am adjusting the value, and will test shortly.

Checking our maps, it does seem like every map is indeed under /Game/Maps/.

[Attachment Removed]

Confirmed that ScanPrimaryAssetTypesFromConfig is called before UpdateManagementDatabase…

Forgot to check for Map… will loop back around to look for that.

[Attachment Removed]