AI coding assistants can now write whole functions, refactor modules and fix failing tests from a description. Used well, they remove a lot of tedious work. Used carelessly, they produce code that compiles, passes a quick glance and fails in production in ways nobody on the team understands, because nobody on the team wrote it.
The difference is rarely the model. It is the workflow around it. This guide describes a workflow for AI-assisted coding that produces changes a careful reviewer would approve: how to scope tasks, what context to provide, why tests should come first, how to review AI-written diffs, and which bugs to look for.
Where AI coding assistants help most, and least
Assistants are strongest where the task is well specified and the surrounding conventions are visible:
- boilerplate, glue code and data mapping;
- writing tests for existing behaviour;
- mechanical refactors such as renames, API migrations and type annotations;
- explaining unfamiliar code and drafting documentation;
- first drafts of scripts and one-off tools.
They are weakest where correctness depends on knowledge that is not in the prompt: business rules, implicit invariants, performance characteristics of your infrastructure, security assumptions, and anything that requires understanding why the system is shaped the way it is. That is not a reason to avoid them there, but it is where review must be strictest.
Step 1: scope the task to one reviewable change
The single most effective habit is asking for small changes. A task that produces a 40-line diff can be reviewed properly; a 900-line diff will be skimmed. Break work into steps that each leave the codebase working:
- Add the new data type and its tests.
- Add the function that produces it.
- Wire it into the endpoint behind a flag.
- Remove the old path.
This is the same decomposition principle that helps with any long task; see decomposition prompts for long, messy tasks.
Step 2: give the assistant the context a new teammate would need
Models write code that matches what they can see. If they cannot see your conventions, they invent their own. Before asking for code, provide:
- the relevant files, not just the function being changed: callers, types and an example of a similar existing feature;
- project conventions: error handling style, logging, naming, the test framework;
- constraints: "no new dependencies," "must run on Node 20," "this function is called in a hot loop."
Many tools support a persistent instructions file for a repository. Treat it like a system prompt: short, specific and about things that actually go wrong. Our anatomy of a production system prompt applies directly.
Step 3: write or generate the test first
Ask for tests before implementation, review the tests yourself, then ask for an implementation that makes them pass. Tests are shorter and easier to review than code, and reviewing them forces you to decide what "correct" means before you see a plausible implementation that might anchor your judgement.
The last line matters: it surfaces assumptions you can confirm or correct before they become code.
Step 4: ask for a diff, with reasons
Request changes as a diff or a clearly delimited set of edits, and ask the assistant to explain each change in one sentence. Unexplained changes outside the requested scope, such as reformatted files, renamed variables or "improved" error handling, are a common source of regressions and noisy reviews. Tell the assistant explicitly not to touch unrelated code.
Step 5: review AI-written code like a stranger's pull request
Review with more suspicion than you would for a trusted colleague, not less. The code will look confident and idiomatic, which makes errors easier to miss.
| Check | What goes wrong |
|---|---|
| Does it call real APIs? | Invented functions, options or library versions |
| Edge cases | Empty inputs, nulls, time zones, Unicode, very large values |
| Error handling | Swallowed exceptions, generic catch-alls, missing retries |
| Security | Unsanitised input in queries, secrets in logs, overly broad permissions |
| Concurrency | Shared state, race conditions, missing locks or transactions |
| Tests | Tests that assert the implementation rather than the requirement |
| Scope | Unrelated edits mixed into the change |
Be especially wary of tests written in the same session as the implementation. A model that misunderstands a requirement will write a test that encodes the same misunderstanding, and both will pass.
Step 6: run everything, then read the failures yourself
Run the full test suite, the linter, the type checker and, where relevant, the application. When something fails, read the error before pasting it back to the assistant. Often the fix is obvious, and understanding it keeps you in control of the codebase. Repeatedly pasting errors back without reading them is how teams end up with code nobody can maintain.
Prompts that work well for common coding jobs
A handful of request shapes cover most day-to-day work. Keep them in a shared snippets file so the whole team benefits.
- Explain before editing. "Explain what this module does, its inputs, outputs and side effects, and list anything that looks surprising." Reading the explanation often reveals that the change you planned is in the wrong place.
- Minimal change. "Make the smallest change that fixes this failing test. Do not refactor. Explain why the test failed."
- Migration. "Here is one file migrated from the old API to the new one. Apply exactly the same transformation to the following file and list any place where the pattern did not apply cleanly."
- Review my diff. "Review this diff as a strict senior engineer. List bugs, missing edge cases and security issues, most severe first. Do not comment on style."
- Write the docstring last. After the code is final, ask for documentation that describes the behaviour, including error cases. Mismatches between the docstring and the code are a cheap bug detector.
Each of these keeps the human in charge of what changes and why, which is the core of the workflow.
When to let an agent run on its own
Agentic coding tools can edit files, run commands and iterate until tests pass. They are productive for well-tested codebases and tightly scoped tasks. Give them guardrails: a clean branch, a task with a clear done condition, permission limits on commands, and a human review before merge. The general trade-offs between autonomous agents and fixed workflows are covered in do you need an agent, or just a workflow?.
Team practices that keep quality high
- Same review bar for all code. No "it's AI-generated, it's probably fine," and no "it's AI-generated, reject it." Judge the diff.
- Authors own their changes. Whoever opens the pull request must be able to explain every line.
- Note AI involvement in the PR description when it is substantial, so reviewers know where to look harder.
- Track escaped defects. If bugs from AI-written code reach production more often, tighten scoping and testing rather than banning the tools.
- Never paste secrets or proprietary customer data into tools whose data handling you have not checked.
Frequently asked questions
Is AI-generated code safe to use in production?
It can be, if it goes through the same review, testing and security checks as any other code. The risk comes from skipping review because the code looks correct, not from where it was written.
How do I get better code from an AI coding assistant?
Give it the relevant files and conventions, keep each task small, ask for tests first, request a diff with explanations, and state constraints such as allowed dependencies and runtime versions explicitly.
Should AI write the tests and the code?
It can write both, but review the tests first and separately. Tests written alongside the implementation tend to share its misunderstandings, so a human should confirm the tests describe the real requirement.
What are the most common bugs in AI-written code?
Calls to non-existent functions or options, missing edge cases, overly broad error handling, security issues with unsanitised input, and unrequested changes outside the task's scope.



