Note: I used Claude to troubleshoot and craft the repro and description.
Steps to reproduce
A const type parameter on an interface call signature stops capturing a string-literal property from the argument object when the parameter's type is wrapped in a homomorphic mapped type (Omit<...>). The literal widens to string, collapsing a downstream conditional type and producing a spurious TS2353.
Minimal repro (depends on TanStack Router, which uses this pattern for its route-bound Route.redirect, whose options type is Omit<RedirectOptions<…>, 'from'>): https://github.com/IanVS/tsgo-error-repro
repro.ts
import { createRootRoute, createRoute, createRouter, redirect } from "@tanstack/react-router";
const rootRoute = createRootRoute();
const agentRoute = createRoute({ getParentRoute: () => rootRoute, path: "/agents/$agentName" });
const agentIndexRoute = createRoute({ getParentRoute: () => agentRoute, path: "/" });
const sessionRoute = createRoute({ getParentRoute: () => agentRoute, path: "/$sessionId" });
const routeTree = rootRoute.addChildren([agentRoute.addChildren([agentIndexRoute, sessionRoute])]);
const router = createRouter({ routeTree });
declare module "@tanstack/react-router" {
interface Register { router: typeof router; }
}
// A) plain redirect, no `from` — OK on both compilers
redirect({
to: "/agents/$agentName/$sessionId",
params: { agentName: "a", sessionId: "s" },
});
// B) plain redirect WITH explicit `from` (options NOT Omit-wrapped) — OK on both compilers
redirect({
from: "/agents/$agentName/",
to: "/agents/$agentName/$sessionId",
params: { agentName: "a", sessionId: "s" },
});
// C) route-bound redirect — options type is Omit<RedirectOptions<…>, "from">
agentIndexRoute.redirect({
to: "/agents/$agentName/$sessionId",
params: { agentName: "a", sessionId: "s" },
});
All three calls are valid: sessionId is a real path param of /agents/$agentName/$sessionId, supplied correctly. The only difference between case B (accepted everywhere) and case C (rejected by tsgo) is that C's options parameter is typed Omit<RedirectOptions<…>, 'from'> instead of an unwrapped object — isolating the mapped-type wrapper as the trigger.
The relevant declaration in @tanstack/router-core:
interface RedirectFnRoute<in out TDefaultFrom extends string = string> {
<TRouter extends AnyRouter, const TTo extends string | undefined = undefined, ...>(
opts: Omit<RedirectOptions<TRouter, TDefaultFrom, TTo, ...>, 'from'> // <-- mapped type
): Redirect<...>;
}
Behavior with typescript@6.0
No errors (verified with typescript@6.0.3). All three calls type-check.
Behavior with tsgo
Case C is rejected:
repro.ts(31,29): error TS2353: Object literal may only specify known properties, and 'sessionId'
does not exist in type 'ParamsReducerFn<RouterCore<…>, ...> | { ... }'.
The to-dependent type parameter resolves as string rather than the literal "/agents/$agentName/$sessionId", so the params type collapses to the parent route's params ({ agentName }) and sessionId is rejected.
Regression bisect (@typescript/native-preview):
| Build |
Result |
7.0.0-dev.20260612.1 |
OK |
7.0.0-dev.20260613.1 |
TS2353 (first bad build) |
7.0.0-dev.20260617.2 |
TS2353 |
7.0.0-dev.20260622.1 |
TS2353 (latest) |
Note: I used Claude to troubleshoot and craft the repro and description.
Steps to reproduce
A
consttype parameter on an interface call signature stops capturing a string-literal property from the argument object when the parameter's type is wrapped in a homomorphic mapped type (Omit<...>). The literal widens tostring, collapsing a downstream conditional type and producing a spuriousTS2353.Minimal repro (depends on TanStack Router, which uses this pattern for its route-bound
Route.redirect, whose options type isOmit<RedirectOptions<…>, 'from'>): https://github.com/IanVS/tsgo-error-reprorepro.tsAll three calls are valid:
sessionIdis a real path param of/agents/$agentName/$sessionId, supplied correctly. The only difference between case B (accepted everywhere) and case C (rejected by tsgo) is that C's options parameter is typedOmit<RedirectOptions<…>, 'from'>instead of an unwrapped object — isolating the mapped-type wrapper as the trigger.The relevant declaration in
@tanstack/router-core:Behavior with
typescript@6.0No errors (verified with
typescript@6.0.3). All three calls type-check.Behavior with
tsgoCase C is rejected:
The
to-dependent type parameter resolves asstringrather than the literal"/agents/$agentName/$sessionId", so the params type collapses to the parent route's params ({ agentName }) andsessionIdis rejected.Regression bisect (
@typescript/native-preview):7.0.0-dev.20260612.17.0.0-dev.20260613.17.0.0-dev.20260617.27.0.0-dev.20260622.1