Most of the bad AI output I see comes from the same root cause, and it's not a bad prompt. The model produced a confident, generic, or flat-out wrong answer because it was starved of context and filled the gap with an average-of-the-internet guess. The prompt was fine. The model simply couldn't see what you can see. That gap between what you know and what's actually in the context window is where nearly every disappointing result lives.
The model only knows what is in the window
A language model has no standing knowledge of your project. It can't see your codebase, your last three architecture decisions, the constraint your ops team handed down in a meeting, or the naming convention you've used consistently for years. It knows two things: whatever it absorbed during training, which is generic and frozen in time, and whatever you put in the context window right now. That's the entire universe it reasons over.
The mental model I use is a sharp contractor dropped into your codebase for the first time, at night, holding a narrow flashlight beam. Genuinely capable. Fast. But the beam only illuminates what you point it at. Everything outside the beam doesn't exist as far as this contractor is concerned. If you ask "why is the checkout flow slow" and shine the light on one controller, you will get an answer about that one controller, delivered with total confidence, whether or not the real problem is three services away in the dark.
This is why prompt cleverness has a low ceiling. Rephrasing the question moves the flashlight a few degrees. Adding the right file turns on the room lights. The second one wins every time, and it's not close.
What "context" actually means
Context isn't a vague vibe you set at the top of a chat. It's a specific, listable set of things the model needs to do the job correctly:
- The relevant files. The actual source, not your paraphrase of it. If the bug is in how two modules interact, both modules belong in the window.
- The real error text. The full stack trace, the exact compiler message, the actual failing test output, copied verbatim. Line numbers and all.
- The constraints. The things that are true about your situation but invisible in the code: "this has to run on the sandboxed build," "we cannot add dependencies," "this table has 40 million rows so the query has to use the index."
- The conventions. How you do things here. The pattern you want followed, not the average pattern from the training set.
- A concrete example of the target pattern. One existing file that already does the thing correctly is worth a paragraph of description.
When people say an AI "just doesn't get it," what's almost always missing is one or more items from that list. The model isn't confused. It's under-informed, and it doesn't know that it's under-informed, so it answers anyway.
Point at files, do not describe them
The single highest-leverage habit is to stop describing your code and start pointing the model at it. "We have a service that syncs emails into contact notes and it dedupes by message ID" is a description. It's lossy, it's your memory of the code rather than the code, and it quietly omits the three edge cases you forgot about. Handing over the actual file is lossless.
In practice this is why file-aware tools beat chat windows for real work. A CLI agent that can open the files you name, or an MCP server that exposes your systems as tools, lets the model pull the real source instead of working from your summary. My CRM email-sync agents don't get told how the dedup logic works; they read the code that does it. The difference in output quality isn't subtle.
The same principle applies to errors. Don't tell the model "it throws a null pointer somewhere in the parser." Paste the stack trace. The trace names the file, the line, and the call chain that got there. You're handing over a map instead of asking the contractor to guess where the noise is coming from. I've watched a five-minute debugging session collapse to thirty seconds purely because the actual exception text went into the window instead of a description of it.
State the constraints you take for granted
The most damaging missing context is the constraint you've internalized so deeply you forget it's not written down anywhere. You know this app runs sandboxed. You know you can't add a new library because of the audit process. You know that column is nullable because of a legacy migration. The model knows none of it, and none of it is visible in the file you pasted.
Left in the dark, the model does the reasonable generic thing: it reaches for a common library, it assumes the field is always populated, it writes the solution that works in the average case on the average project. Then you reject it, rephrase, and try again, when the actual fix was one sentence of constraint you never supplied. State the non-obvious constraints up front, before the model has spent its reasoning on a solution that was dead on arrival.
This is also the argument for durable, written context that outlives a single chat. Constraints and conventions that apply to every task in a project belong in a file the model reads every time, not in your head where you have to remember to retype them. That's exactly the job of a CLAUDE.md project-instructions file and an authoritative reference library: the non-obvious rules get stated once, in a place the model always sees, so you're not re-supplying them by hand on every request.
More context is not automatically better
There's a real tradeoff on the other side, so this isn't "dump everything and let the model sort it out." Attention is finite. When you pad the window with ten files where two are relevant, the eight irrelevant ones don't help; they dilute. The signal you care about now competes with noise, and the model can anchor on a stale file or an unrelated helper and drag the answer sideways.
The goal is the relevant context, not the maximum context. That means:
- Include the files that bear on the task and leave out the ones that don't.
- Paste the failing test, not the entire test suite.
- Give the one example that demonstrates the pattern, not the twelve that also happen to use it.
- Clear the window between unrelated tasks so yesterday's problem isn't haunting today's.
Curating context is a skill of its own, and it's closely tied to scoping a task to fit the context window in the first place. A task small enough to hold its relevant files, its constraints, and its target pattern all at once, without the window bloating into noise, is a task the model can actually do well. A sprawling task where the relevant context doesn't fit is one you should decompose before you even start.
Why good context beats better wording
Here's the practical payoff. When output disappoints, the reflex is to iterate on the prompt: reword it, add "please be careful," append "think step by step," try a firmer tone. That's tuning the flashlight's angle. Sometimes it helps a little. Usually it doesn't, because the problem was never the phrasing; it was that the room was dark.
The higher-leverage move is almost always to add context rather than polish wording. Pull in the file you were describing. Paste the actual error. State the constraint you assumed was obvious. Show the one example of the pattern you want. I would take a blunt, plainly worded prompt with the right five files over an elegantly engineered prompt working from a vague description, every single time, and it's not a close call.
None of this is exotic. It's the discipline of remembering that the model sees far less than you do, and that closing the gap is your job, not the model's. Point the beam where the work actually is. The contractor is sharp. Light the room, and you will be surprised how little cleverness the prompt needs.