{"record":{"id":"77c6f03c9bfb2659","repo":"microsoft/aspire","slug":"dashboard-run-storedrun-runid-is-no-longer-available","errorCode":null,"errorMessage":"Dashboard run '{storedRun.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":295,"sourceCode":"            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\");\n        var metadata = JsonSerializer.Deserialize<DashboardRunMetadata>(File.ReadAllText(metadataPath));\n        if (metadata?.SchemaVersion != run.SchemaVersion ||\n            !string.Equals(metadata.RunId, run.RunId, StringComparison.Ordinal))\n        {\n            throw new InvalidDataException($\"Dashboard run metadata for '{run.RunId}' is invalid.\");\n        }\n\n        var updatedMetadata = metadata with { IsPinned = isPinned };\n        WriteMetadata(updatedMetadata, metadataPath);\n        if (string.Equals(run.RunId, RunId, StringComparison.Ordinal))\n        {","sourceCodeStart":277,"sourceCodeEnd":313,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Dashboard/ServiceClient/DashboardRunStore.cs#L277-L313","documentation":"Inside SetRunPinned, after resolving the stored run, the store tries to take a temporary file lock on the run's directory (needed only for unselected, non-current historical runs). If TryOpenRunLock returns null — the lock file cannot be opened because the run is being deleted or its directory is inaccessible — the null-coalescing throw raises InvalidOperationException.","triggerScenarios":"Pinning a historical run while it is concurrently being deleted or its run lock file cannot be acquired/opened (TryOpenRunLock returned null).","commonSituations":"Race between retention cleanup deleting an old run and a user pinning it in the dashboard; run directory removed externally while metadata update is in flight; file-system permission problems on the run directory.","solutions":["Retry the pin operation after re-checking the run still exists in the store","Check that no concurrent deletion/pruning is running when pinning historical runs","Verify file-system permissions on the runs directory allow creating/opening lock files"],"exampleFix":"// before: pin without checking availability again\nstore.SetRunPinned(run, isPinned: true);\n\n// after: catch the race and refresh\ntry\n{\n    store.SetRunPinned(run, isPinned: true);\n}\ncatch (InvalidOperationException)\n{\n    runs = store.ListRuns(); // run vanished; refresh UI state\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for (var attempt = 0; attempt < 3; attempt++)\n{\n    try\n    {\n        store.SetRunPinned(run, isPinned: true);\n        break;\n    }\n    catch (InvalidOperationException ex) when (ex.Message.Contains(\"no longer available\"))\n    {\n        await Task.Delay(200); // run may be mid-deletion; recheck then give up\n    }\n}","preventionTips":["Avoid deleting/pruning runs while pin operations are in flight","Ensure the runs directory grants write access for lock files","Retry transient lock failures rather than treating them as permanent"],"tags":["dashboard","file-lock","race-condition","persistence"],"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"}