LykosAI/StabilityMatrix · error · InvalidOperationException

Relative URL did not contain a route path

Error message

Relative URL did not contain a route path

What it means

BuildDetailDataPath converts a relative model URL into the Next.js data path (e.g. /models/{id}.json) and throws InvalidOperationException when the resulting URL path is empty after trimming trailing slashes — meaning the relative URL had no route component.

Solutions

  1. Pass a URL containing a route path, e.g. "/models/12345"
  2. Strip query/fragment and rebuild from the model ID if only those parts exist
  3. Pre-validate that the URL's path is non-empty before calling

Example fix

// before
BuildDetailDataPath("?modelVersionId=99");
// after
BuildDetailDataPath("/models/12345?modelVersionId=99");
Defensive patterns

Strategy: validation

Validate before calling

if (string.IsNullOrWhiteSpace(url) || !url.TrimStart('/').Split('?')[0].Split('#')[0].Contains('/')) throw new ArgumentException("URL has no route path");

Type guard

bool HasRoutePath(string? url) => !string.IsNullOrWhiteSpace(url) && new Uri(CivArchiveApiClient.BaseUri, url).AbsolutePath.TrimEnd('/').Length > 0;

Try / catch

try { var path = CivArchiveApiClient.BuildDetailDataPath(url); }
catch (InvalidOperationException ex) { Logger.Warn(ex, "URL {Url} has no route path", url); return null; }

Prevention

When it happens

Trigger: Calling BuildDetailDataPath (directly or via GetModelDetailsAsync) with a URL like "", "?query=1", or a fragment-only reference so uri.AbsolutePath resolves to "/" and trims to empty.

Common situations: Passing query-string or fragment strings instead of route paths, malformed stored URLs missing the path component, base-URI-only references.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of LykosAI/StabilityMatrix@af93d6ef57 (2026-09-12). Data as JSON: /api/errors/4f95206d16fd09cd. Report an issue: GitHub.

Appendix: source

Thrown at StabilityMatrix.Core/Api/CivArchiveApiClient.cs:337

        {
            query.Add($"type={Uri.EscapeDataString(string.Join(",", filters.Types))}");
        }

        if (filters.BaseModels.Count > 0)
        {
            query.Add($"base_model={Uri.EscapeDataString(string.Join(",", filters.BaseModels))}");
        }

        return $"{NormalizeRoutePath(routePath)}.json?{string.Join("&", query)}";
    }

    public static string BuildDetailDataPath(string relativeUrl)
    {
        var uri = new Uri(BaseUri, relativeUrl);
        var path = uri.AbsolutePath.TrimEnd('/');
        if (string.IsNullOrWhiteSpace(path))
        {
            throw new InvalidOperationException("Relative URL did not contain a route path");
        }

        // Apply Next.js rewrite rules: /{platform}/models/{id}/versions/{versionId}
        // rewrites to /models/{id}?modelVersionId={versionId}&platform={platform}
        var rewriteMatch = PlatformDetailRewriteRegex().Match(path);
        if (rewriteMatch.Success)
        {
            var platform = rewriteMatch.Groups["platform"].Value;
            var modelId = rewriteMatch.Groups["modelId"].Value;
            var versionId = rewriteMatch.Groups["versionId"].Value;

            return $"/models/{modelId}.json?modelVersionId={versionId}&platform={platform}";
        }

        // Also handle /{platform}/models/{id} (without version)
        var platformModelMatch = PlatformModelRewriteRegex().Match(path);
        if (platformModelMatch.Success)
        {

View on GitHub (pinned to af93d6ef57)