AI Programming Pitfalls: Don't Let Fallback Code Fake Success

11/03 2026 402

Series: "Writing High-Quality Code with AI: AI Werewolf Development Record." This article covers versions v1.0 → v1.2.1.

Preface

WorkBuddy ran a "1024 Creation Challenge" app-building contest for Programmer's Day: build an app with WorkBuddy and publish it to the event page to enter, in either the web app or mini-program category. Prizes are point-based—4,000 points for first place, 3,000 for second, 2,000 for third, 500 for participation, plus merchandise.

The result: in two days, 119 commits, 13 versions, and about 12,000 lines of code, evolving from a "one-sentence requirement" into a complete app with 5 lying AIs, friend invitations, and shareable battle-record cards. Along the way I logged nine "crash moments"; this article covers the first and most typical one.

The question this article answers: why does the first version of AI-written code "look like it runs but is actually fake"?

Introduction: One sentence, twenty minutes, a clickable game

What I told it was very brief:

Goal: 1. Build a Werewolf web game. There is 1 human player, and AI fills the remaining seats. AI speeches, voting, and night actions must actually call the large model—no pre-written lines.

The other five fields—background, process, requirements, materials, and output location—I left all blank at the time. Basically every problem later in this article grew out of those unfilled fields.

Twenty minutes later I had a playable version: the homepage had a moon and a wolf, the setup page let you choose the number of players, and in a match you could watch the AIs speak in turn, vote, and reach a settlement.

Figure: The v1.2.1 homepage (run from the commit at that time). A wolf head, a codename input, a model selector, and below that a line reading "Large model channel normal · 1.6s"—it already looked like a real product.

The Problem: After nine games, I realized the lines were "recorded"

That afternoon I played nine games. By the ninth, something felt off: no matter who was the wolf, no matter the situation, the AI spoke with the same flavor—all saying "I think X is more suspicious, I'll hear what others say first," all two sentences long, all starting from "speaking order."

I assumed the prompt was too generic, opened the code to add personality, and then saw this section:

fallbackSpeech is a local script library. If a call fails, times out, or returns the wrong format—any one of these—it picks a line from the library as a substitute, then marks it as degraded. And that mark is not shown on the interface at all.

The match page at that time looked like this (also run from v1.2.1):

Figure: Speech log from Day 1. Doudou and Qixi both opened with "peaceful night," both threw out feelings first and then followed votes—their style differences showed up only in modal particles.

So in those nine games, even I didn't know how many lines were actually generated by the model.

This is not a bug; this is a "thoughtful lie." It doesn't report errors, doesn't crash, and looks completely normal.

To be honest: that piece of code only lived for three hours. By the first commit (30b7288) it had already been deleted, so no historical screenshot remains—the only evidence I can offer is the code above and that day's observation of "nine games with identical dialogue." All screenshots later in this article are from after the fix.

Root Cause: AI's default action is "don't let it crash"

Figure: Two outcomes of a single AI speech. "Success" goes left and gets the real line; "failure" goes right, picks a line from the script library as a substitute, then flags it with a marker not displayed on the interface. Both paths merge into the same exit—so what you see is always "everything normal."

The first layer is the model's preference. When writing code, the model leans on patterns learned from historical code and is prone to over-engineering, specifically: "don't report errors"—add try/catch, add default values, add ?? 'default text'.

The second layer is the actual scenario. What we actually need to build requires "every line to be truly generated by the model." If a model call fails, there should be a friendly prompt that lets us perceive it—the red dashed line in the figure should give some friendly prompt.

So abnormal failure is the norm, not an accident. The real question, then, is not "how to make it not fail" but "what to do when it fails." Three ways of handling it correspond to three levels of quality:

Solution: Remove the escape route and make failure light up

Figure: The same flow after modification. The left path still lets true words through; the right path no longer looks for a substitute, but directly throws an error, aborts the match, and writes the reason into the event stream. The two paths no longer merge—you always know which one you are facing.

The real change in this step is not "easier to use" but "diagnosable." Before, it was a black box that looked normal; afterward, every exception has a name.

The cost is that a match may be aborted outright because of network jitter—at least three kinds of failure actually came up: output is not valid JSON (most common), call timeout, and returning 5xx. The first time I hit it, my instinct was to "add retries," but I later found that retries only reduce probability: sometimes the model really does output a piece of explanatory text wrapping the JSON. The real solution is fault-tolerant parsing (parse directly first; if that fails, extract the first {...} from the text).

Effect: Channel status visible at a glance

The most direct change after the fix is an extra "Channel Status" line on the homepage:

Figure: Channel self-check on the v1.2.1 homepage. It actually calls the model once on every load, reporting the result, elapsed time, and model name—with this line present, I won't again finish nine games before discovering I was playing a fake match.

Summary

AI's default preference when writing code is "keep the program from crashing,"

so in scenarios requiring authenticity, it will actively hide failures.

Make failure visible

Far better than making failure disappear is throwing errors + aborting + diagnosability + friendly prompts, which is an order of magnitude higher in quality.

What methodology can be drawn from this?

Any pipeline of "AI-generated content → shown to users" (review, customer service, reports, completion) should consider observability. Don't let the model directly provide fallback; instead, give handling and prompts for different errors, and do logging + observability properly.

References

Solemnly declare: the copyright of this article belongs to the original author. The reprinted article is only for the purpose of spreading more information. If the author's information is marked incorrectly, please contact us immediately to modify or delete it. Thank you.