microsoft/aspire · error · InvalidOperationException
The deployment state file must have a parent directory.
Error message
The deployment state file must have a parent directory.
What it means
Aspire derives a lock path from the deployment state file's parent directory to serialize deployments across AppHost instances. Path.GetDirectoryName on the fully qualified state path returned null (only possible at a filesystem root with no parent), so the code throws InvalidOperationException because a lock location cannot be computed.
Solutions
- Move the deployment state file into a proper subdirectory (e.g. <workdir>/state/deployment-state.json) so it has a parent directory.
- Update the deployment state manager configuration so StateFilePath points at a nested path, not a filesystem root.
- If running in a container, ensure the state file lives under a mounted working directory with at least one parent, not the mount root itself.
Example fix
// before StateFilePath = "/deployment-state.json"; // root-level, no parent // after StateFilePath = "/state/deployment-state.json";
Defensive patterns
Strategy: validation
Validate before calling
var full = Path.GetFullPath(stateFilePath);
if (Path.GetDirectoryName(full) is not { Length: > 0 })
throw new InvalidOperationException("State file must not be at a filesystem root."); Try / catch
try { await deployment.DeployAsync(...); }
catch (InvalidOperationException ex) when (ex.Message.Contains("must have a parent directory"))
{ /* move StateFilePath into a nested directory and retry */ } Prevention
- Place state files under a dedicated subdirectory of the working directory, never at a root.
- Validate configured state paths at startup (require a non-empty parent directory).
- In containers, mount state under a working directory with ancestors, not at the mount root.
When it happens
Trigger: Configuring the deployment state manager's StateFilePath to a path whose resolved parent is null — practically, a state file placed directly at a drive/filesystem root (e.g. '/state.json' or 'C:\state.json') such that the deploymentsDirectory resolution collapses to null.
Common situations: Hand-editing state path configuration to a root-level path, or container/mount setups where the state file sits at the mount root with no ancestor directory.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- The connection string
- Unsupported Azure sandbox tier
- A BlobServiceClient could not be configured. Ensure valid…
- A BlobServiceClient could not be configured. Ensure valid…
- A ChatCompletionsClient could not be configured. Ensure…
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/8ca2ee04f7811b6c.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Hosting.Azure.Sandboxes/AzureSandboxContainerDeployment.cs:856
})
]
};
}
internal static async Task<FileLock?> AcquireDeploymentLeaseAsync(
IDeploymentStateManager deploymentStateManager,
string appHostIdentity,
string environmentName,
string stateSectionName,
CancellationToken cancellationToken)
{
if (deploymentStateManager.StateFilePath is not { Length: > 0 } stateFilePath)
{
return null;
}
var stateDirectory = Path.GetDirectoryName(Path.GetFullPath(stateFilePath))
?? throw new InvalidOperationException("The deployment state file must have a parent directory.");
var deploymentsDirectory = Path.GetDirectoryName(stateDirectory) ?? stateDirectory;
var lockIdentity = $"{appHostIdentity}\0{environmentName.ToLowerInvariant()}\0{stateSectionName}";
var lockName = XxHash3.HashToUInt64(Encoding.UTF8.GetBytes(lockIdentity)).ToString("x16", CultureInfo.InvariantCulture);
var lockPath = Path.Combine(
deploymentsDirectory,
".locks",
$"azure-sandbox-{lockName}.lock");
return await FileLock.AcquireAsync(lockPath, cancellationToken).ConfigureAwait(false);
}
private static IEnumerable<string> GetOutboundHttpHosts(string value)
{
if (TryGetOutboundHttpHost(value, out var host))
{
return [host];
}
View on GitHub (pinned to 25830f84bd)