clockworklabs/SpacetimeDB · error · ArgumentException

Route conflict for `{path}`

Error message

Route conflict for `{path}`

What it means

AddRoute throws ArgumentException when the candidate route's path is byte-equal to an existing route's path AND the methods overlap: same method on both, or either side registered with Any (Any matches all methods). Different methods on the same path are legal. Because Router methods accumulate routes via CloneRoutes, every Get/Post/... call on a chain re-checks against everything registered before it.

Source

Thrown at crates/bindings-csharp/Runtime/Router.cs:109

        return new Router(merged);
    }

    private List<RouteSpec> CloneRoutes() => [.. routes];

    private static void AddRoute(
        List<RouteSpec> routes,
        MethodOrAny method,
        string path,
        string handlerFunction
    )
    {
        AssertValidPath(path);
        ArgumentException.ThrowIfNullOrEmpty(handlerFunction);

        var candidate = new RouteSpec(method, path, handlerFunction);
        if (routes.Exists(route => RoutesOverlap(route, candidate)))
        {
            throw new ArgumentException($"Route conflict for `{path}`", nameof(path));
        }

        routes.Add(candidate);
    }

    private static string JoinPaths(string prefix, string suffix)
    {
        if (prefix == "/")
        {
            return suffix;
        }
        if (suffix == "/")
        {
            return prefix;
        }

        prefix = prefix.TrimEnd('/');
        suffix = suffix.TrimStart('/');

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Find and remove the duplicate registration (the exception's path names the offending route)
  2. If the second registration was meant for a different method, keep it — only same-method or Any overlaps conflict; drop the Any and use explicit methods
  3. Deduplicate shared routes when merging routers assembled from separate modules

Example fix

// before
var router = Router.New()
    .Get("/users", listUsers)
    .Any("/users", handleUsers); // Any overlaps the Get above

// after
var router = Router.New()
    .Get("/users", listUsers)
    .Post("/users", createUser); // different method: no conflict
Defensive patterns

Strategy: validation

Validate before calling

// Keep (method, path) pairs you have registered and reject duplicates before calling the Router:
bool IsRegistered(HashSet<(string Method, string Path)> seen, string m, string p) => seen.Contains((m, p)) || seen.Contains(("ANY", p)) || (m == "ANY" && seen.Any(k => k.Path == p));

Try / catch

try { router = router.Get(path, handler); }
catch (ArgumentException e) when (e.Message.StartsWith("Route conflict"))
{ /* duplicate method+path (or Any overlap): remove or rename the route */ }

Prevention

When it happens

Trigger: Registering Get("/x") twice on the same chain; Any("/x") followed by Post("/x") (or the reverse); a Nest whose joined sub-router path equals an already-registered path with an overlapping method; Merge of two routers that share a method+path pair.

Common situations: Copy-pasted route registrations; route tables assembled from multiple modules that both define '/health' or '/metrics'; mixing an Any catch-all with explicit routes on the same path.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16). Data as JSON: /api/errors/59d1082cad729f38. Report an issue: GitHub.