{"record":{"id":"a9d54032e9a0bff7","repo":"stride3d/stride","slug":"rundirectory-has-no-parent-app-directory","errorCode":null,"errorMessage":"'{runDirectory}' has no parent app directory.","messagePattern":"'(.+?)' has no parent app directory\\.","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"sources/crashreport/Stride.CrashReporter/CrashSession.cs","lineNumber":53,"sourceCode":"    public IntPtr OwnerWindow { get; }\n\n    /// <summary>The deduped crash groups in this run that are not already suppressed.</summary>\n    public IReadOnlyList<StoredCrash> Groups { get; }\n\n    /// <summary>The run directory on disk (for a \"reveal in file manager\" that also keeps the files).</summary>\n    public string RunDirectory => run.Directory;\n\n    /// <summary>Crash sending was turned off at build time (StrideSentryDsn=false); offer no Send.</summary>\n    public bool IsDisabled => CrashReportSender.IsDisabled;\n\n    /// <summary>\n    /// Loads a run directory. The store layout is <c>&lt;base&gt;/&lt;app&gt;/run-*</c>, so the app id and base\n    /// are the run's parent and grandparent — enough to also reach the app's suppression list.\n    /// </summary>\n    public static CrashSession Load(string runDirectory, string? dsnOverride, bool sessionScoped, int? hostProcessId = null, IntPtr ownerWindow = default)\n    {\n        var full = Path.GetFullPath(runDirectory);\n        var appDir = Directory.GetParent(full) ?? throw new ArgumentException($\"'{runDirectory}' has no parent app directory.\");\n        var baseDir = appDir.Parent ?? throw new ArgumentException($\"'{runDirectory}' has no store base directory.\");\n\n        var store = new CrashStore(appDir.Name, baseDir.FullName);\n        var run = CrashStore.OpenRun(full);\n        var dsn = CrashReportSender.ResolveDsn(dsnOverride);\n\n        // Defensive: capture already skips suppressed signatures, but never re-surface one that slipped through.\n        var groups = run.Read().Where(crash => !store.IsSuppressed(crash.Signature, crash.Version)).ToList();\n        return new CrashSession(store, run, dsn, sessionScoped, hostProcessId, ownerWindow, groups);\n    }\n\n    /// <summary>True when a full-memory dump of the crashed host can be written on demand: the host itself crashed\n    /// (<c>--host-pid</c>) and is still alive, blocked until this window closes. Windows only (dbghelp).</summary>\n    public bool CanDumpHost => hostProcessId is not null && OperatingSystem.IsWindows();\n\n    /// <summary>\n    /// Writes a full-memory dump of the waiting host to <paramref name=\"path\"/>, with the report text beside it (a\n    /// dump only makes sense with its context). Strictly on demand: it can be several GB and is unscrubbed, so it","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/crashreport/Stride.CrashReporter/CrashSession.cs#L35-L71","documentation":"CrashSession.Load expects a run directory laid out as <base>/<app>/run-*. The run directory's parent must be the app directory; if the path has no parent (e.g. a filesystem root), the app id cannot be determined and the load aborts with this ArgumentException.","triggerScenarios":"Calling CrashSession.Load on a path that is a root directory or otherwise has no parent (Directory.GetParent returns null), instead of a run-* folder nested under an app directory.","commonSituations":"Passing the wrong level of the store hierarchy (the base or app directory, or \"/\") instead of a run directory; programmatic path building that concatenates incorrectly.","solutions":["Pass the actual run directory (e.g. <base>/<app>/run-2024-01-01) to Load","Guard the input: ensure Path.GetFullPath(runDirectory) has a parent before calling Load","Fix any path-building code that points at the root or the base directory"],"exampleFix":"// before\nvar session = CrashSession.Load(storeBaseDir, dsn, false); // base dir, wrong level\n// after\nvar runDir = Directory.GetDirectories(storeBaseDir, \"*\", SearchOption.AllDirectories)\n    .First(d => Path.GetFileName(d).StartsWith(\"run-\"));\nvar session = CrashSession.Load(runDir, dsn, false);","handlingStrategy":"validation","validationCode":"var full = Path.GetFullPath(runDirectory);\nif (Directory.GetParent(full) is null)\n    throw new ArgumentException($\"'{runDirectory}' is not a valid run directory (no parent app directory)\");","typeGuard":"bool IsLikelyRunDirectory(string path) =>\n    Path.GetFileName(path).StartsWith(\"run-\") && Directory.GetParent(Path.GetFullPath(path)) != null;","tryCatchPattern":"try { var session = CrashSession.Load(runDir, dsn, false); }\ncatch (ArgumentException e) when (e.Message.Contains(\"no parent app directory\"))\n{ /* correct the path or re-locate the run directory */ }","preventionTips":["Always pass the leaf run-* directory, never the base or app directory","Discover run directories via Directory.EnumerateDirectories(base, \"*\") and pick run-* entries","Assert the expected <base>/<app>/run-* layout in tests"],"tags":["crash-reporting","filesystem","path"],"backgroundTag":"directory-not-found","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}