{"record":{"id":"63a0893d060cb8aa","repo":"LykosAI/StabilityMatrix","slug":"relative-url-is-required","errorCode":null,"errorMessage":"Relative URL is required","messagePattern":"Relative URL is required","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"StabilityMatrix.Core/Api/CivArchiveApiClient.cs","lineNumber":166,"sourceCode":"            };\n\n            cachedFilterOptions = new CacheEntry<CivArchiveFilterOptions>(DateTimeOffset.UtcNow, result);\n            return result;\n        }\n        finally\n        {\n            filterOptionsLock.Release();\n        }\n    }\n\n    public async Task<CivArchiveModelDetailsResponse> GetModelDetailsAsync(\n        string relativeUrl,\n        CancellationToken cancellationToken = default\n    )\n    {\n        if (string.IsNullOrWhiteSpace(relativeUrl))\n        {\n            throw new ArgumentException(\"Relative URL is required\", nameof(relativeUrl));\n        }\n\n        var nextDataPath = BuildDetailDataPath(relativeUrl);\n\n        if (detailsCache.Get(nextDataPath) is { } cachedDetails && cachedDetails.IsFresh(DetailsCacheTtl))\n        {\n            return cachedDetails.Value;\n        }\n\n        var response = await GetNextDataAsync<CivArchiveDetailPageResponse>(nextDataPath, cancellationToken);\n\n        var pageProps =\n            response.PageProps\n            ?? throw new InvalidOperationException(\"CivArchive detail page was missing pageProps\");\n\n        // A version-less /models/{id} data request returns a Next.js redirect payload pointing\n        // at the model's primary version instead of pageProps.model — follow it like the\n        // browser would. Guarded against self-redirects so a server quirk can't loop us.","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/LykosAI/StabilityMatrix/blob/af93d6ef57c01cd890d7e0ad0a9ea8c9fcda3002/StabilityMatrix.Core/Api/CivArchiveApiClient.cs#L148-L184","documentation":"GetModelDetailsAsync validates its relativeUrl parameter before fetching the model's Next.js data page, throwing ArgumentException for null, empty, or whitespace URLs. The client needs a real route path (e.g. /models/12345) to locate the __NEXT_DATA__ payload.","triggerScenarios":"Calling GetModelDetailsAsync with an empty string, null, or whitespace-only relativeUrl — typically when an upstream model URL field was never populated or was stripped during parsing.","commonSituations":"Model records imported from older data versions missing the page URL, a failed earlier parse yielding '', passing a raw ID instead of a URL without formatting.","solutions":["Ensure the model's relative URL (e.g. \"/models/{id}\") is populated before calling","Construct the URL from the model ID if only an ID is available: $\"/models/{id}\"","Guard the call site against empty URLs before invoking"],"exampleFix":"// before\nawait api.GetModelDetailsAsync(model.PageUrl); // PageUrl may be \"\"\n// after\nif (string.IsNullOrWhiteSpace(model.PageUrl)) return null;\nawait api.GetModelDetailsAsync(model.PageUrl);","handlingStrategy":"validation","validationCode":"if (string.IsNullOrWhiteSpace(model.PageUrl)) { Logger.Warn(\"Model {Id} has no page URL\", model.Id); return null; }","typeGuard":"bool HasRelativeUrl(string? url) => !string.IsNullOrWhiteSpace(url) && url.StartsWith('/');","tryCatchPattern":"try { return await api.GetModelDetailsAsync(url); }\ncatch (ArgumentException ex) { Logger.Warn(ex, \"Missing relative URL\"); return null; }","preventionTips":["Populate the page URL when importing models","Derive the URL from the model ID instead of storing an optional field","Validate stored records for empty URL fields on upgrade"],"tags":["argument-validation","api","null-argument"],"backgroundTag":"empty-required-field","analyzedSha":"af93d6ef57c01cd890d7e0ad0a9ea8c9fcda3002","analyzedAt":"2026-09-12T19:02:43.389Z","contentChangedAt":"2026-09-12T19:02:43.389Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}