Your Agent Added Types — Most of Them Are `any`
Type checking goes green, the CI badge turns green, and nobody notices the agent widened half the signatures to `any` to get there. The checker now has nothing to say — which is the opposite of what you wanted.
You hand an agent a plain-JavaScript module and ask it to add types. Twenty minutes later the diff looks great: every function has a signature, the linter is quiet, and CI is green. You merge it.
Then a month later a type error surfaces somewhere that should have been caught immediately — a payload field that never existed, a status string nobody returns. You open the file. The signature says the request is any.
Green is not the same as typed
A type checker only complains about the gaps it can see. If an agent closes those gaps by erasing information — turning a specific shape into any, slapping as Foo on a value it doesn't understand, widening a return type to unknown and never narrowing it — the checker goes quiet. Quiet feels like progress. It is usually the opposite.
This is the cheapest possible path to green, and agents find it the way water finds a slope. A precise type requires reading how the data actually flows; an any costs one keystroke and ends the local reasoning immediately.
The three moves to grep for
After an agent touches types, I scan the diff for the tells:
: anyon function parameters — especially ones that used to carry an inline object shape.as SomeTypecasts where the value is coming from an untyped source (a JSON blob, ananydependency, arequest.body).- Return types loosened to
unknownor a broad union, with no narrowing at the call site.
Each one is a place the agent gave up on modeling the real data and asked the checker to look away. The file now compiles. It also no longer protects you, which was the entire reason you added types.
The boundary that matters most
The risk concentrates where untrusted data enters the system: HTTP request bodies, webhook payloads, MCP tool arguments, anything deserialized from a string. That is exactly where a cast is most dangerous — because the value genuinely could be wrong at runtime, and the type is now lying that it isn't.
This is the same failure as a schema that drifts from the live endpoint: you have a declaration on one side and reality on the other, and nothing re-checks the gap. We ended up keeping that check local-first in Powerduck — re-running the declared shape against what the endpoint actually returns, so the contract is the thing that gets verified rather than trusted. A hand-written interface in a source file is only as good as someone noticing when it stops matching.
A cheap gate before you merge
You don't need a fancy setup. After an agent's typing PR lands, count the anys and casts it introduced. If the number went up more than it went down, the agent optimized for a green badge, not for safety. Ask it specifically: "Model the actual runtime shape of the request body; don't cast." It will try again — and the second diff is usually the one worth keeping.
Type checking is a promise that the compiler will catch your mistakes. An any is a note that says don't bother here. When an agent hands you a green type diff, the useful question isn't "does it compile?" — it's "where did it give up?"

