{"record":{"id":"70074643fda72e7d","repo":"microsoft/aspire","slug":"cannot-materialize-terminal-hosts-apphost-filepath-apphost","errorCode":null,"errorMessage":"Cannot materialize terminal hosts: AppHost:FilePath / AppHost:Path is not set in configuration.","messagePattern":"Cannot materialize terminal hosts: AppHost:FilePath / AppHost:Path is not set in configuration\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting/TerminalResourceBuilderExtensions.cs","lineNumber":178,"sourceCode":"        if (replicaCount < 1)\n        {\n            replicaCount = 1;\n        }\n\n        // All per-replica terminal-host files live flat under ~/.aspire/trmnl/, with\n        // a random per-run replica id. This:\n        //  - matches the repo's convention for per-user runtime state (cf. ~/.aspire/cli/bch,\n        //    ~/.aspire/dev-certs, ~/.aspire/deployments)\n        //  - avoids dropping UDS sockets in the global /tmp on Linux where different distros\n        //    treat /tmp permissions differently\n        //  - keeps absolute paths short enough to fit sun_path (104 bytes on macOS)\n        //  - lets external tools enumerate by listing {trmnlDir}/{id}.metadata.json\n        //  - prevents an old child or AppHost cleanup from touching a replacement run's sockets.\n        var configuration = @event.Services.GetRequiredService<IConfiguration>();\n        var appHostPath = configuration[\"AppHost:FilePath\"] ?? configuration[\"AppHost:Path\"];\n        if (string.IsNullOrEmpty(appHostPath))\n        {\n            throw new InvalidOperationException(\n                \"Cannot materialize terminal hosts: AppHost:FilePath / AppHost:Path is not set in configuration.\");\n        }\n\n        var trmnlDirectory = configuration[TerminalHostPaths.DirectoryOverrideConfigName];\n        if (string.IsNullOrEmpty(trmnlDirectory))\n        {\n            var homeDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);\n            trmnlDirectory = TerminalHostPaths.GetTrmnlDirectory(homeDirectory);\n        }\n\n        // 0700 on Unix so other local users cannot enumerate which terminals exist on\n        // this machine. On Windows the user-profile ACLs (per-user by default) make this\n        // a no-op; CreateDirectory is idempotent.\n        try\n        {\n            DirectoryHelper.CreateWithOwnerOnlyPermissions(trmnlDirectory);\n        }\n        catch (Exception ex) when (ex is UnauthorizedAccessException or IOException)","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting/TerminalResourceBuilderExtensions.cs#L160-L196","documentation":"MaterializeTerminalHostsAsync runs at AppHost start and needs the AppHost's own file path from configuration to lay out per-run terminal host files/sockets. This InvalidOperationException is thrown when neither AppHost:FilePath nor AppHost:Path is present in configuration, meaning the terminal host materialization cannot compute its target locations.","triggerScenarios":"Running an AppHost with WithTerminal-configured resources in an environment where the AppHost configuration keys AppHost:FilePath/AppHost:Path were never set — e.g. custom hosting of the DistributedApplication, test harnesses, or trimming of AppHost configuration.","commonSituations":"Hosting the AppHost programmatically in tests or tools instead of via the standard aspire run pipeline, or configuration being rebuilt/overwritten before the BeforeStartEvent fires.","solutions":["Run the AppHost through the standard entry point (aspire run / DistributedApplication.Run) which sets AppHost:FilePath","Set configuration[\"AppHost:FilePath\"] (or AppHost:Path) to the AppHost project path in custom hosts/tests before starting","Verify no custom IConfiguration setup removes or renames the AppHost configuration section"],"exampleFix":"// before (custom test host)\nvar app = builder.Build();\nawait app.RunAsync();\n// after\nbuilder.Configuration[\"AppHost:FilePath\"] = appHostProjectPath;\nvar app = builder.Build();\nawait app.RunAsync();","handlingStrategy":"validation","validationCode":"var appHostPath = config[\"AppHost:FilePath\"] ?? config[\"AppHost:Path\"];\nif (string.IsNullOrEmpty(appHostPath))\n    throw new InvalidOperationException(\"AppHost:FilePath must be set before enabling terminals in custom hosts.\");","typeGuard":null,"tryCatchPattern":"try { await MaterializeTerminalHostsAsync(@event, ct); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"AppHost:\")) { logger.LogError(ex, \"Terminal host materialization skipped: AppHost path missing from configuration\"); }","preventionTips":["Run AppHosts through the standard aspire run entry point","Set AppHost:FilePath explicitly in custom hosts and test harnesses","Avoid rebuilding IConfiguration in ways that drop AppHost keys"],"tags":["aspire","configuration","terminal","apphost"],"backgroundTag":"missing-required-config-field","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}