A while ago I accepted a Copilot suggestion that looked exactly right. It was idiomatic, it compiled, it passed code review. Weeks later I found out it was subtly wrong, in a way that never showed up in tests and only surfaced under real conditions.
That bug changed the question I ask about Copilot. Not “is it good?” It clearly is, for a lot of work. The better question is: where does it not belong?
This article is my answer. Not a list of hot takes, but a checklist of situations where I have learned to decline the suggestion, and the single rule that connects them.
The pattern behind every Copilot failure
Every situation in this article is a variation of one rule:
Never accept code you cannot judge.
Copilot produces plausible code. Plausible is not the same as correct, and the gap between the two is invisible in the editor. The only thing standing between a plausible suggestion and your main branch is your ability to verify it.
That means Copilot’s usefulness is not a property of the tool. It is a property of the situation. The same suggestion can be a time-saver for one developer and a liability for another, depending entirely on whether the person accepting it can tell right from almost-right.
Keep that lens in mind. Each of the five situations below is a place where verification ability drops, the cost of being wrong rises, or both.
Unfamiliar domains: where you can’t tell right from plausible
The most tempting time to lean on Copilot is when you are working in a domain you don’t know well. A new language, an unfamiliar framework, a protocol you have never implemented.
It is also the worst time.
In a domain you know, Copilot is an accelerator. You recognize correct code on sight, so you accept good suggestions in seconds and dismiss bad ones just as fast. Verification is nearly free.
In a domain you don’t know, the economics invert. Copilot produces plausible code much faster than you can build the knowledge needed to evaluate it. You end up with working-looking code and no way to judge it. The suggestions all look reasonable, because “looks reasonable to a non-expert” is precisely what a language model is optimized to produce.
This is how you ship code that is wrong in ways you don’t have the vocabulary to detect yet. Concurrency code with race conditions you can’t see. Database queries that work until the table grows. API integrations that ignore an edge case the documentation warns about on a page you never read.
The practical takeaway: in unfamiliar territory, read the documentation and write the first version yourself, slowly. Use Copilot once you know enough to disagree with it. If you can’t imagine rejecting a suggestion, you are not qualified to accept one.
Security-sensitive code: where plausible is dangerous
Authentication, cryptography, input handling. This is the worst-case combination: the stakes are high and the failure modes are invisible.
Here is a suggestion very much like ones Copilot produces for webhook verification:
function verifyWebhookSignature(payload, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return signature === expected;
}
This code looks careful. It uses the right algorithm, derives the expected signature correctly, and compares the two values. It would pass most code reviews.
It is still wrong. The === comparison short-circuits: it returns as soon as it finds the first byte that differs. That means the comparison takes measurably longer the more leading characters an attacker gets right, which turns your response time into an oracle for guessing valid signatures byte by byte.
The correct version uses a constant-time comparison:
function verifyWebhookSignature(payload, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest();
const provided = Buffer.from(signature, "hex");
if (provided.length !== expected.length) return false;
return crypto.timingSafeEqual(provided, expected);
}
Notice what happened here. The flaw was not a syntax error or a missing null check, the categories of mistake reviewers are trained to spot. It was a domain-specific property of the comparison operator that only matters in an adversarial context.
Copilot is trained on public code, and most public code gets this wrong. In security-sensitive contexts, the statistically common answer is often the vulnerable one. That is exactly the code Copilot has learned to suggest with confidence.
For auth, crypto, session handling, and input sanitization: write it yourself against the official documentation, or use a well-audited library. If Copilot contributes anything here, treat it as untrusted input.
Generated complexity: when the code works but shouldn’t exist
Not every Copilot failure is a bug. Some of the most expensive ones are code that works perfectly and never should have been written.
Copilot optimizes for a locally complete answer: a self-contained block that fully handles the prompt in front of it. It does not optimize for the simplest answer, and it does not know what your codebase already contains.
Ask for “a function that deduplicates items by a key” and you often get something like this:
function deduplicateByKey(items, key, options = {}) {
const { keepLast = false, comparator = null } = options;
const seen = new Map();
for (const item of items) {
const k = item[key];
if (comparator) {
let found = false;
for (const existing of seen.keys()) {
if (comparator(existing, k)) { found = true; break; }
}
if (found && !keepLast) continue;
}
if (!seen.has(k) || keepLast) {
seen.set(k, item);
}
}
return Array.from(seen.values());
}
The version an experienced developer writes:
function uniqueBy(items, key) {
return [...new Map(items.map(i => [i[key], i])).values()];
}
The generated version is not wrong. It handles options nobody asked for, supports a comparator no caller will ever pass, and adds a code path that will never be tested. Every accepted suggestion like this raises the maintenance cost of the codebase a little. Multiply by hundreds of suggestions across a team and a year, and you have a codebase that is significantly harder to read, test, and change, without a single bug to point at.
The takeaway is a habit: when a suggestion is longer than you expected, that is a signal to pause, not to be impressed. Ask what the simplest version looks like, and whether the function should exist at all.
False confidence: the review problem nobody talks about
Here is the risk I see discussed least: not the quality of the suggestions, but what they do to the person reviewing them.
When a junior colleague submits code, you read it critically. You expect mistakes, so you look for them. The rough edges in the code, the slightly-off naming, the hesitant structure, all of it keeps your skepticism engaged.
Copilot output has none of those signals. It arrives fluently formatted, idiomatically named, confidently structured. It looks like code written by someone who knew what they were doing. And so you read it the way you read a senior colleague’s code: skimming for structure rather than checking for correctness.
This is backwards. The generated code deserves more scrutiny than the junior colleague’s code, not less. The junior developer at least understood the requirements, ran the code, and had something at stake. The model did none of those things. Fluency is a property of the training data, not evidence of correctness.
The failure compounds at the pull request level. A PR full of generated code reads smoothly, so it reviews quickly, so more of it gets merged, so reviewers see even more fluent code and calibrate their attention even lower.
The countermeasure is simple to state and genuinely hard to practice: review generated code as if a stranger wrote it. Because one did.
When Copilot is simply slower than typing
Some situations are not dangerous at all. Copilot is just slower than doing the work yourself, and it is worth recognizing them so you stop paying the overhead.
Every accepted suggestion has a hidden loop attached: prompt, read, verify, fix. For boilerplate, that loop is cheap and the typing saved is large. But three kinds of tasks reliably invert the math:
- Debugging existing behavior. The work is understanding what the code actually does at runtime. Copilot can only see the text, and generating plausible-looking fixes for a bug you don’t understand yet just adds candidates to rule out.
- Precise refactors. When you know exactly what needs to change, describing it, reading the suggestion, and verifying it preserved behavior takes longer than making the edit. Your IDE’s rename and extract tools are deterministic; Copilot is not.
- Changes that depend on context Copilot can’t see. Team conventions, the reason a workaround exists, the incident that shaped an odd design decision. Suggestions here are confidently wrong in ways that take real time to check.
None of these are failures of the model. They are situations where verification costs more than generation saves. Noticing them is not an anti-AI stance. It is the same judgment you apply to any tool.
A verification-first decision rule
The five situations reduce to one judgment call, made on two axes: how well can I verify this output, and what does it cost if I’m wrong?
quadrantChart
x-axis Low cost of failure --> High cost of failure
y-axis Weak verification ability --> Strong verification ability
quadrant-1 Copilot plus rigorous review
quadrant-2 Let Copilot drive
quadrant-3 Slow down and learn first
quadrant-4 Write it yourself
Boilerplate and test scaffolding: [0.2, 0.85]
Generated complexity: [0.35, 0.65]
Tasks slower than typing: [0.25, 0.35]
Unfamiliar domains: [0.6, 0.2]
Security-sensitive code: [0.85, 0.3]
Reading the quadrants:
- Strong verification, low cost: let Copilot drive. Boilerplate, test scaffolding, code in your home domain. This is where the productivity stories are true.
- Strong verification, high cost: use Copilot, but review its output like a hostile audit, and make tests carry the proof.
- Weak verification, low cost: fine for throwaway experiments, but be honest that you are learning from an unreliable teacher. Verify against real documentation before anything sticks.
- Weak verification, high cost: write it yourself. Security code in a domain you half-know sits here, and it is the quadrant where accepted suggestions become incidents.
Before accepting a nontrivial suggestion, the whole rule fits in one question: if this is subtly wrong, would I catch it, and what happens if I don’t?
Declining suggestions is an engineering skill
The bug from the introduction was never really a Copilot problem. It was a verification problem. I accepted code faster than I could judge it, and the tool did exactly what it was built to do: produce something plausible.
Knowing when to decline AI assistance is the same skill as knowing when a green test suite actually proves something, or when a code review approval means “verified” versus “looks fine.” It is quality engineering judgment applied to a new source of code.
Copilot did not change the fundamentals. Every line in your codebase is one you own, whoever or whatever wrote it. The developers getting the most out of these tools are not the ones who accept the most suggestions. They are the ones who know, before the tab key goes down, whether they are in a situation where they can tell right from plausible.
That judgment is the checklist. The five situations are just the places where it matters most.