{"record":{"id":"9ffcb708d0e01fae","repo":"microsoft/aspire","slug":"dashboard-run-run-runid-is-no-longer-available","errorCode":null,"errorMessage":"Dashboard run '{run.RunId}' is no longer available.","messagePattern":"Dashboard run '(.+?)' is no longer available\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/Aspire.Dashboard/ServiceClient/DashboardRunStore.cs","lineNumber":284,"sourceCode":"    public DashboardPersistenceMode PersistenceMode { get; }\n    public bool SupportsRunSelection => PersistenceMode == DashboardPersistenceMode.Run;\n\n    public IReadOnlyList<DashboardRunDescriptor> GetRuns() =>\n        _runs.Value.Where(run => !run.IsPruned).ToArray();\n\n    public DashboardRunDescriptor GetCurrentRun() => GetRuns().Single(run => run.IsCurrent);\n\n    public DashboardRunDescriptor? GetRunById(string runId, bool onlyCompatible) =>\n        GetRuns().SingleOrDefault(run =>\n            (!onlyCompatible || run.IsCompatible) &&\n            string.Equals(run.RunId, runId, StringComparison.Ordinal));\n\n    public void SetRunPinned(DashboardRunDescriptor run, bool isPinned)\n    {\n        var storedRun = GetRunById(run.RunId, onlyCompatible: false);\n        if (storedRun is null)\n        {\n            throw new InvalidOperationException($\"Dashboard run '{run.RunId}' is no longer available.\");\n        }\n\n        var runDirectory = Path.GetDirectoryName(storedRun.DatabasePath)!;\n        lock (_runStateLock)\n        {\n            // The current run has the store's lifetime lock, and a selected historical run has a lease.\n            // Only an unselected historical run needs a temporary lock while its metadata is updated.\n            using var runLock = storedRun.IsCurrent || storedRun.IsLeased\n                ? null\n                : TryOpenRunLock(runDirectory)\n                    ?? throw new InvalidOperationException($\"Dashboard run '{storedRun.RunId}' is no longer available.\");\n            UpdatePinnedState(storedRun, runDirectory, isPinned);\n        }\n    }\n\n    private void UpdatePinnedState(DashboardRunDescriptor run, string runDirectory, bool isPinned)\n    {\n        var metadataPath = Path.Combine(runDirectory, \"run.json\");","sourceCodeStart":266,"sourceCodeEnd":302,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Dashboard/ServiceClient/DashboardRunStore.cs#L266-L302","documentation":"SetRunPinned updates a historical run's pinned flag, first resolving the run's stored record via GetRunById. It throws InvalidOperationException when no stored run exists for the given run ID — the run has been deleted or pruned from the store, so pinning cannot proceed.","triggerScenarios":"Calling SetRunPinned with a DashboardRunDescriptor whose RunId is no longer present in the run store (GetRunById returned null, even with onlyCompatible:false).","commonSituations":"A UI or automation holds a reference to a run that was concurrently deleted/expired by retention cleanup; pinning a historical run after the store was disposed or its directory removed; stale run descriptor from a previous dashboard session.","solutions":["Re-list available runs and confirm the run ID exists before pinning","Catch the exception and refresh the run list in the caller (the run was deleted externally)","Avoid pinning runs that are not present in the store's current run inventory"],"exampleFix":"// before\nstore.SetRunPinned(run, isPinned: true);\n\n// after\nif (store.GetRunById(run.RunId, onlyCompatible: false) is not null)\n{\n    store.SetRunPinned(run, isPinned: true);\n}","handlingStrategy":"try-catch","validationCode":"if (store.GetRunById(run.RunId, onlyCompatible: false) is null)\n{\n    // run no longer exists; skip pinning and refresh the run list\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    store.SetRunPinned(run, isPinned: true);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"no longer available\"))\n{\n    runs = store.ListRuns(); // refresh stale UI state\n}","preventionTips":["Re-resolve runs from the store instead of holding long-lived descriptors","Refresh the run list after retention cleanup or session changes","Treat run IDs as ephemeral; validate existence before operations"],"tags":["dashboard","run-not-found","persistence","stale-reference"],"backgroundTag":"record-not-found","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"}