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

  1. Move the deployment state file into a proper subdirectory (e.g. <workdir>/state/deployment-state.json) so it has a parent directory.
  2. Update the deployment state manager configuration so StateFilePath points at a nested path, not a filesystem root.
  3. 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

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


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)