DEV Community

Jeff
Jeff

Posted on Originally published at powerduck.com

The type check passed, so the agent shipped a value shape the runtime never produced

A feature took a response from an internal service and mapped it into a view model for the UI. The agent added an interface for the raw payload, an interface for the model, and a mapping function between them. It ran the type checker, saw zero errors, and marked the task done. On staging the screen rendered empty: the service returns { "data": { "items": [] } }, while the declared type exposed items at the top level. The compiler never complained, because the loose index signature and optional fields on both sides had made the mismatch invisible.

The agent had checked the code against the type it wrote, not against the response that actually arrived. The green check was real evidence that the mapping compiles; it was not evidence that the producer's shape matched it.

Why a green type check reads as done

Types are compile-time promises that disappear before the code runs. The compiler compares what the author declared against what the code references; it never reaches out to the service, opens a real response, or counts the keys that come back. When the agent pairs this with a loose record type or optional fields, a missing or nested key stops producing an error without stopping the mismatch.

Optional fields are the quietest culprit. Declaring a key optional says that absence is allowed, so the producer omitting it or nesting it deeper becomes legal at compile time. The same pattern on the mapping output means a name that renders blank never trips the check; it only shows up as an empty cell in the interface.

The loop the agent runs reinforces the habit. It writes the type, writes the code to match, re-runs the checker, and stops on green. There is no second step that compares the written type to a captured real payload, so the loop never teaches the agent that compile silence and runtime correctness are different claims.

What ships when types stand in for checks

  • Undefined fields render blank. A missing nested key reaches the component as undefined; nothing throws, so the failure looks like empty data rather than a broken mapping.
  • Loose signatures paper over the gap. An index signature or permissive optional field lets a producer shape the compiler never modeled pass right through.
  • Green CI hides the drift. The test suite runs the mapping against a fixture the agent itself wrote, which uses the same wrong shape; real producer drift never enters the run.
  • The pattern repeats across calls. If the agent concludes a passing check means done, every subsequent integration stops at tsc instead of observing the response.
  • The failure surfaces far from the source. A payload mismatch written in the client appears on a user screen long after the agent closed the task, when the trace back to the mapping has gone cold.

The tell is a completion message that cites the type check rather than an observed sample. "It compiles" answers the compiler's question; it does not answer whether the outside world returned the expected value.

Check the runtime shape against a real sample

Treat the written type as a hint, not the contract. Verify the boundary against data that actually moved:

  • Capture one real response and assert on it. Run the mapping against a captured sample payload with the real wrapper, not a hand-built fixture; check that the expected keys exist at the expected depth and are not empty.
  • Put a runtime schema on the boundary. A schema parser such as Zod validates the actual incoming object and rejects a missing or nested key at runtime, which a compile-time type cannot.
  • Drop the loose index signature in fresh code. Explicit required fields force a mismatch to surface instead of being absorbed by a catch-all record.
  • Let the error name the missing key. A runtime validation failure reports the exact path that was absent, instead of leaving the UI to render a blank the user cannot diagnose.
  • Compare the live response to the documented request. When the agent drives an outbound call, the response shape has to be checked against the payload that really came back; we keep that step local in Powerduck, re-running the documented request against the live endpoint and reading the actual body instead of trusting a handwritten type that drifted.

A compiler answers the code's question, not the world's

Type checking verifies that the code you wrote agrees with itself. It cannot verify that the service on the other side agreed back. Until a runtime check or a real sample proves the shape arrived, a green check means the code compiled, not that the feature worked.

Top comments (0)