vibeapp.win

What broke

A type cast hid a missing field, and every sale showed a dash

Claude used a TypeScript type assertion to tell the compiler a field existed when the database query never fetched it, so the code compiled and every sale in the stock app's result column showed "—". Henning found it when the result was missing from the transactions page. The rule: don't use an `as` cast to force a field into existence; select the field and let the type system check it.

The short version

What went wrongA TypeScript `as` cast claimed a field existed that the query never selected, so the lookup matched nothing and every sale in the result column showed the fallback dash.

How we found itHenning, who owns the app, noticed the result was missing from the transactions on the sims after the feature was deployed. The code had compiled fine.

The rule nowDon't use an `as` cast to force a field into existence; add the field to the query's select and let the type system check it, so a missing field becomes a compile error.

This post documents something Claude got wrong. The claim rests on a commit or a file, not on memory. All of Claude's mistakes

On the morning of 31 July 2026 Claude added a column to the stock app showing realized profit or loss on each sale in a simulation. It was deployed, and every sale in it showed “—”. The fix was committed eight minutes after the feature.

Henning, who owns the app, reported it. His report is quoted in the fix commit. Translated from Norwegian, it read: “I don’t see it on the transactions on the sims?” The commit’s next line adds: “It was deployed, so it was a real bug.”

The commit says the code compiled fine.

What Claude got wrong

The fix commit is short. Its title, translated: “the result column was EMPTY - I cast away the type check that would have caught it.” Claude wrote the code and the commit message, and the message says plainly who made the mistake.

There were two faults, and each one hid the other.

First, the database query that loads a simulation’s transactions never selected the instrument ID. That field is the key used to match realized profit and loss to each transaction. The calculation produces one result per sale, keyed on instrument and execution time, and the page looks each transaction up by that same key. Without the instrument ID, no lookup could match.

Second, at the point where the code read that field, Claude did not let the compiler check it. It wrote a type assertion that told TypeScript the transaction object had an optional instrument ID. The commit puts it this way (translated): it “TOLD the compiler that the field existed instead of letting it check.”

If the field had been read directly, the compiler would have refused: the selected row had no such property. The assertion made that error go away. At runtime the field was undefined, the lookup key had nothing where the instrument should be, nothing matched, and every sale got the fallback value.

The commit message sums up what the cast did (translated): “An as cast tells the compiler what to believe; it verifies nothing.”

Why it looked plausible

The dash was not a random error marker. The column shows “—” on purpose for buys, because a buy has no realized gain yet, and the code comments note that 0 would be misleading there. So a column of dashes looked like a state the page was designed to show. It just should not have covered the sales.

That is the failure mode of a type assertion. It doesn’t crash. It replaces a compile-time error with a quiet runtime value, and here that value looked like correct output.

How it was fixed

The fix in the simulation route does two things:

  • It adds the instrument ID to the query’s select, because realized P&L is keyed on it.
  • It removes the cast. The lookup now reads the field directly and only runs if the field has a value, so the compiler checks that the key exists.

Now that the field comes from the select, the type system can catch it again. If someone drops it from the query later, the code stops compiling instead of showing dashes.

The part Claude misunderstood first

The same commit also covers a second change, labeled (translated) “the part I misunderstood first.” The commit does not say what the first reading was. It says the order log on the trading page now also shows realized results in kroner on sales.

Here is how it works, according to the commit. The paper broker returns the transaction ID as the broker order ID, so a filled order points straight at the transaction that moved the money. Orders that were blocked, rejected or run as dry-runs have no transaction. They get “—”, not 0, because they never moved any kroner.

This part reuses the same FIFO calculation for realized profit as the simulation page instead of adding a new one. The commit message notes it in one line (translated): “Still no fifth implementation.”

What we take from it

This was one incident, and we don’t draw conclusions about how Claude writes TypeScript in general. The mechanics are clear, though, and worth recording.

A type assertion is a claim, not a check. When code needs a field the data doesn’t have, an assertion is the shortest way to make the compiler stop complaining. It is also the way to make sure nobody finds the problem until a person looks at the screen. Here, the compiler was the one tool that would have caught the missing field before deployment, and the cast turned it off for exactly that line.

Henning found it by reporting that he couldn’t see the result on the simulation transactions. That works, but it’s the last line of defence, not the first.

The rule

Don’t use an as cast to force a field into existence. If the code needs a field, add it to the query’s select and let the type system check that it is there. If the compiler says the field is missing, that is the bug, and the fix belongs in the query, not in the type.