{"record":{"id":"8ca2ee04f7811b6c","repo":"microsoft/aspire","slug":"the-deployment-state-file-must-have-a-parent-directory","errorCode":null,"errorMessage":"The deployment state file must have a parent directory.","messagePattern":"The deployment state file must have a parent directory\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting.Azure.Sandboxes/AzureSandboxContainerDeployment.cs","lineNumber":856,"sourceCode":"                })\n            ]\n        };\n    }\n\n    internal static async Task<FileLock?> AcquireDeploymentLeaseAsync(\n        IDeploymentStateManager deploymentStateManager,\n        string appHostIdentity,\n        string environmentName,\n        string stateSectionName,\n        CancellationToken cancellationToken)\n    {\n        if (deploymentStateManager.StateFilePath is not { Length: > 0 } stateFilePath)\n        {\n            return null;\n        }\n\n        var stateDirectory = Path.GetDirectoryName(Path.GetFullPath(stateFilePath))\n            ?? throw new InvalidOperationException(\"The deployment state file must have a parent directory.\");\n        var deploymentsDirectory = Path.GetDirectoryName(stateDirectory) ?? stateDirectory;\n        var lockIdentity = $\"{appHostIdentity}\\0{environmentName.ToLowerInvariant()}\\0{stateSectionName}\";\n        var lockName = XxHash3.HashToUInt64(Encoding.UTF8.GetBytes(lockIdentity)).ToString(\"x16\", CultureInfo.InvariantCulture);\n        var lockPath = Path.Combine(\n            deploymentsDirectory,\n            \".locks\",\n            $\"azure-sandbox-{lockName}.lock\");\n\n        return await FileLock.AcquireAsync(lockPath, cancellationToken).ConfigureAwait(false);\n    }\n\n    private static IEnumerable<string> GetOutboundHttpHosts(string value)\n    {\n        if (TryGetOutboundHttpHost(value, out var host))\n        {\n            return [host];\n        }\n","sourceCodeStart":838,"sourceCodeEnd":874,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting.Azure.Sandboxes/AzureSandboxContainerDeployment.cs#L838-L874","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nStateFilePath = \"/deployment-state.json\"; // root-level, no parent\n// after\nStateFilePath = \"/state/deployment-state.json\";","handlingStrategy":"validation","validationCode":"var full = Path.GetFullPath(stateFilePath);\nif (Path.GetDirectoryName(full) is not { Length: > 0 })\n    throw new InvalidOperationException(\"State file must not be at a filesystem root.\");","typeGuard":null,"tryCatchPattern":"try { await deployment.DeployAsync(...); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"must have a parent directory\"))\n{ /* move StateFilePath into a nested directory and retry */ }","preventionTips":["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."],"tags":["azure","sandboxes","filesystem","configuration"],"backgroundTag":"invalid-config-value","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T09:17:21.228Z"}