stride3d/stride · error · ArgumentException

' ' has no store base directory.

Error message

'{runDirectory}' has no store base directory.

What it means

Beyond requiring a parent (app) directory, CrashSession.Load also requires a grandparent — the store base directory — so it can locate the app's suppression list and other store data. If the app directory itself has no parent (i.e. it is a root's child), loading aborts with this ArgumentException.

Solutions

  1. Place the crash store at least three levels deep: <base>/<app>/run-*, e.g. C:\Users\me\AppData\Crashes\MyApp\run-1
  2. Validate the layout (run dir has both parent and grandparent) before calling Load
  3. Move the store off the filesystem root if it was deployed there

Example fix

// before
var dir = Path.Combine("C:\\", "run-1"); // grandparent missing
var session = CrashSession.Load(dir, dsn, false);
// after
var dir = Path.Combine(storeBase, "MyApp", "run-1");
var session = CrashSession.Load(dir, dsn, false);
Defensive patterns

Strategy: validation

Validate before calling

var appDir = Directory.GetParent(Path.GetFullPath(runDirectory));
if (appDir?.Parent is null)
    throw new ArgumentException("Run directory must be nested as <base>/<app>/run-*; base directory missing");

Type guard

bool HasStoreLayout(string runDir) {
    var app = Directory.GetParent(Path.GetFullPath(runDir));
    return app?.Parent != null;
}

Try / catch

try { var session = CrashSession.Load(runDir, dsn, false); }
catch (ArgumentException e) when (e.Message.Contains("no store base directory"))
{ /* move/relocate the store off the root */ }

Prevention

When it happens

Trigger: Calling CrashSession.Load on a run directory directly under a filesystem root, e.g. C:\run-x or /run-x, where appDir.Parent is null.

Common situations: Extracting or creating the crash store at a drive/mount root during testing; using temp paths like /tmp-as-root or drive roots instead of a nested store layout.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/7affd5fafd23ce93. Report an issue: GitHub.

Appendix: source

Thrown at sources/crashreport/Stride.CrashReporter/CrashSession.cs:54

    /// <summary>The deduped crash groups in this run that are not already suppressed.</summary>
    public IReadOnlyList<StoredCrash> Groups { get; }

    /// <summary>The run directory on disk (for a "reveal in file manager" that also keeps the files).</summary>
    public string RunDirectory => run.Directory;

    /// <summary>Crash sending was turned off at build time (StrideSentryDsn=false); offer no Send.</summary>
    public bool IsDisabled => CrashReportSender.IsDisabled;

    /// <summary>
    /// Loads a run directory. The store layout is <c>&lt;base&gt;/&lt;app&gt;/run-*</c>, so the app id and base
    /// are the run's parent and grandparent — enough to also reach the app's suppression list.
    /// </summary>
    public static CrashSession Load(string runDirectory, string? dsnOverride, bool sessionScoped, int? hostProcessId = null, IntPtr ownerWindow = default)
    {
        var full = Path.GetFullPath(runDirectory);
        var appDir = Directory.GetParent(full) ?? throw new ArgumentException($"'{runDirectory}' has no parent app directory.");
        var baseDir = appDir.Parent ?? throw new ArgumentException($"'{runDirectory}' has no store base directory.");

        var store = new CrashStore(appDir.Name, baseDir.FullName);
        var run = CrashStore.OpenRun(full);
        var dsn = CrashReportSender.ResolveDsn(dsnOverride);

        // Defensive: capture already skips suppressed signatures, but never re-surface one that slipped through.
        var groups = run.Read().Where(crash => !store.IsSuppressed(crash.Signature, crash.Version)).ToList();
        return new CrashSession(store, run, dsn, sessionScoped, hostProcessId, ownerWindow, groups);
    }

    /// <summary>True when a full-memory dump of the crashed host can be written on demand: the host itself crashed
    /// (<c>--host-pid</c>) and is still alive, blocked until this window closes. Windows only (dbghelp).</summary>
    public bool CanDumpHost => hostProcessId is not null && OperatingSystem.IsWindows();

    /// <summary>
    /// Writes a full-memory dump of the waiting host to <paramref name="path"/>, with the report text beside it (a
    /// dump only makes sense with its context). Strictly on demand: it can be several GB and is unscrubbed, so it
    /// is never written unasked and never sent. False when there is no live host or the dump failed.

View on GitHub (pinned to 96fad776d2)