TypeScript — TS2345 Argument Type Not Assignable
Diagnose and fix TypeScript error TS2345 by reading the full type, narrowing unions, handling optional properties, and avoiding unsafe any casts.
Symptoms
- tsc reports: Argument of type 'X' is not assignable to parameter of type 'Y'
- The editor shows a red squiggle on an argument, but the value looks correct at runtime
- CI fails on tsc --noEmit while the bundler still builds the project
Possible causes
- The argument is a union and only one member is valid for the function
- An optional property is passed where a required property is expected
- Object literals contain excess properties or use a widened string instead of a literal union
- A generic parameter is inferred too broadly, or a dependency's types changed
- A value is null or undefined at the call site
Diagnosis steps
Get the complete error without truncation
npx tsc --noEmit --pretty falseInterpretation
The full diagnostic prints the source type, target type, and the property that differs. Long types are often collapsed by the editor.
Compare the source and target types property by property
npx tsc --noEmit --pretty false 2>&1 | grep -A3 TS2345Interpretation
The message names the first incompatible property. Check optionality, literal vs string, and readonly modifiers.
Inspect the inferred type of the argument
// Hover the value in the editor, or assign it to a typed const to force the error at the declarationInterpretation
If the inferred type is wider than expected, annotate the declaration or use satisfies to preserve literal types.
Root cause
TypeScript rejects the call because the argument's static type does not satisfy the parameter's contract. The fix is to change the value's type, narrow it before the call, or correct the function signature; suppressing the error hides a real mismatch.
Fix
Narrow unions with a discriminant or a type guard, make optional fields explicit, annotate literal unions with as const or satisfies, and align the function signature with the actual contract. Avoid as any and ts-ignore unless the type definition is genuinely wrong and an upstream fix is tracked.
Risk note
Casting through unknown or any moves the failure to runtime. If the value comes from an API, validate it at the boundary instead of asserting its shape.
Verify the fix
Run tsc --noEmit with the same tsconfig as CI. Add a type-level test or a unit test for the narrowed branch, and confirm the fix does not introduce new errors in dependent packages.
Read the second type, not only the first
TS2345 names both sides of the assignment. Most developers read the first type, recognize their value, and stop. The useful information is in the target type: which property is required, which literal is allowed, and whether the parameter accepts a union. The compiler stops at the first incompatibility, so fix that property and rerun before guessing at the rest.
type Status = 'draft' | 'published';
function setStatus(status: Status) {}
const value = 'draft'; // inferred as string, not 'draft'
setStatus(value); // TS2345
The variable is inferred as string because let and object properties widen literals. const value = 'draft' as const or an explicit Status annotation preserves the literal. For object literals, satisfies checks the shape without widening the values:
const config = { status: 'draft' } satisfies { status: Status };
setStatus(config.status); // OK
Narrow before the call
When the argument is a union, a runtime check is also a compile-time narrowing:
type Result = { ok: true; value: string } | { ok: false; error: Error };
function handle(result: Result) {
if (result.ok) {
consume(result.value); // narrowed to the success branch
}
}
A type assertion such as result as { value: string } compiles but does not check the discriminant at runtime. Prefer a guard or a discriminated union. If the data comes from JSON.parse, the assertion is especially dangerous: the parsed value is any, and the compiler cannot verify it.
Fix the signature when the contract is wrong
Sometimes the call site is correct and the parameter is too narrow. If a function accepts string[] but callers pass readonly arrays, change it to readonly string[]. If an optional field is always provided in one flow, split the function or use an overload. Do not add any to the parameter to silence the error; that removes the check for every caller.
Verify in the same configuration
Editor diagnostics can differ from CI if they use different tsconfig files or TypeScript versions. Run the same tsc --noEmit command CI uses, and check that the fix does not suppress errors with skipLibCheck or a broad exclude. A clean local build with a different config is not evidence.