Describing the task

Code generation is using a language model to write source code, scripts, queries or configuration from a description in natural language, usually with existing code as context. The model writes code that fits the description as it understands it. Whatever the description leaves out (the language version, the operating system, how errors should be handled, whether new libraries are allowed) the model decides for you, and you only find out when the code meets your real environment. The tools themselves, from chat assistants to coding agents in the editor, are described on AI coding assistants.

Ravi Kumar, the portal tech lead, needs a small script for the nightly meter-reading exports:

Write a Python script that cleans up old export files.
Write a Python 3.11 script for a Linux server. Standard library only; no third-party packages.

Task: in the folder /srv/exports, delete files named readings-YYYY-MM-DD.csv whose date in the name is more than 30 days before today. Ignore all other files.
Example: on 2026-10-03, readings-2026-09-02.csv is deleted and readings-2026-09-03.csv is kept.

Requirements:
- A --dry-run option that only prints what would be deleted. Make dry run the default; deleting needs --delete.
- Print one line per file: "would delete", "deleted" or "kept".
- If the folder does not exist, print an error and exit with code 1.

Give the script, then three test cases I can run in a temporary folder.

Each line of the specific prompt closes off a guess:

  • Version and environment. "Python 3.11 on Linux" rules out syntax from newer versions and Windows paths.
  • Constraints. "Standard library only" stops the model from adding a dependency your server does not have.
  • A worked example of the boundary case. "More than 30 days" is ambiguous at the edge; the example settles it.
  • Safe defaults. A script that deletes things should not delete on its first run; asking for a dry run as the default reflects how you would test it anyway (see dry runs).
  • Tests in the answer, so checking the script is part of the task, not an afterthought.

When the code has to fit into an existing project, paste the relevant function or file, not the whole repository, and say what must stay as it is: the function signature, the logging style, the error handling. Coding agents that work inside a repository read standing instructions from files such as AGENTS.md or CLAUDE.md; how to write those is on context engineering and agents.

Explain, suggest or change

"Can you look at this function?" can mean "tell me what is wrong", "suggest a fix" or "fix it". Assistants resolve this ambiguity differently, and their makers tune the defaults differently (as of October 2026):

Vendor guidance What it says
Anthropic (Claude) Follows the wording closely: "Can you suggest some changes" may get only suggestions, while "Change this function" gets the change. Ask explicitly for action if you want it.
OpenAI (GPT-6 guide) Recommends prompting GPT-6 to "bias towards action" and to treat requests such as "can you…" or "help me…" as permission to complete the task.

So do not rely on either default. Start with "Explain only; do not change the code" when you want to understand, and with "Make this change and nothing else" when you want an edit. The second half matters: Anthropic notes that some of its models tended to over-engineer, adding files, abstractions and options nobody asked for, and suggests an instruction along the lines of "Avoid over-engineering. Only make changes that are directly requested." Small, requested changes are also far easier to review.

Test-first prompting

Test-first prompting is writing the tests, or a list of inputs with their expected outputs, before asking a model for code, putting them in the prompt, and accepting the generated code only when it passes them. It is the prompting version of test-driven development, and it changes who decides what "correct" means: you, in advance, rather than the model's explanation of its own code after the fact.

Ravi needs a function that finds Kestrel's incident numbers (INC- followed by exactly five digits) in an email subject. He writes five cases first, including the edge cases he knows about, and pastes them into the prompt with the request. The code below contains two recorded answers from an assistant, written as functions, and runs the same cases against both:

import re

# The tests: written first, and pasted into the prompt.
CASES = [
    ("INC-40217 and INC-40218 are linked", ["INC-40217", "INC-40218"]),
    ("Re: INC-40217 (was INC-40217)", ["INC-40217"]),
    ("Change CHG-1187 only", []),
    ("INC-123 is too short", []),
    ("INC-402171 is too long", []),
]


# First answer from the assistant (a recorded answer)
def ticket_ids_v1(text):
    return re.findall(r"INC-\d+", text)


# Second answer, after the failing cases were pasted back (a recorded answer)
def ticket_ids_v2(text):
    return sorted(set(re.findall(r"\bINC-\d{5}\b", text)))


for function in (ticket_ids_v1, ticket_ids_v2):
    failures = 0
    for text, expected in CASES:
        result = function(text)
        if result != expected:
            failures += 1
            print(f"FAIL {function.__name__}({text!r})")
            print(f"     got {result}, expected {expected}")
    print(f"{function.__name__}: {len(CASES) - failures} of {len(CASES)} passed")
FAIL ticket_ids_v1('Re: INC-40217 (was INC-40217)')
     got ['INC-40217', 'INC-40217'], expected ['INC-40217']
FAIL ticket_ids_v1('INC-123 is too short')
     got ['INC-123'], expected []
FAIL ticket_ids_v1('INC-402171 is too long')
     got ['INC-402171'], expected []
ticket_ids_v1: 2 of 5 passed
ticket_ids_v2: 5 of 5 passed

The first answer looked fine and handled the obvious case. The failing cases, pasted back word for word, are the most precise feedback you can give: they say exactly what is wrong without you having to diagnose it. (The \b in the second version is a word boundary; see anchors and word boundaries.)

One risk is specific to this approach: a model under pressure to pass tests can write code that passes these tests and nothing else, for example by special-casing the test inputs. Anthropic's prompting guide suggests saying so directly, along these lines:

Write a general solution that works for all valid inputs, not only the test cases. Do not hard-code values or special-case the test inputs. If you think a test is wrong, tell me instead of working around it.

And keep some cases back: tests the model never saw are the honest check of whether it solved the problem or the test set.

The test-first loop. Write the tests, then a prompt with the task, the tests and the constraints. The model generates code. Run the tests. If any fail, paste the failing cases back into the prompt and repeat. When all pass, run the held-back cases, review the code and only then commit it. Write tests Prompt: task, tests, limits Generated code Run the tests failures: paste the failing cases back all pass: held-back cases, review, commit
The tests decide when the loop ends; the model's explanation of its own code does not.

Debugging with error messages

An error-message prompt is a request for debugging help that contains the exact error message or stack trace, the code and input that produced it, the environment, and what you expected to happen instead. "It doesn't work" forces the model to guess which of a hundred possible problems you have; the exact message usually names it. Paste the message, never a paraphrase: the line number, the exception type and the value in quotes are the clues. How to read a traceback yourself first is worth knowing too, because you can then tell whether the explanation fits.

A good debugging prompt contains what a good help request to a colleague contains, and the minimal reproducible example is the gold standard for both. You can even build the prompt in code, so the error text is copied exactly:

import traceback


def parse_reading(raw):
    return int(raw.strip())


try:
    parse_reading("12,5")
except ValueError as error:
    error_text = "".join(traceback.format_exception_only(error)).strip()

prompt = f"""\
Environment: Python 3.11 on Linux, standard library only.
Goal: parse_reading() should turn a meter reading typed by a customer into a number.
Input that fails: "12,5" (customers in the Netherlands write a decimal comma).
Error:
{error_text}

Code:
def parse_reading(raw):
    return int(raw.strip())

Explain the cause in two sentences. Then show the smallest change that accepts
"12,5" and "12.5" as 12.5. Do not change anything else."""

print(prompt)
Environment: Python 3.11 on Linux, standard library only.
Goal: parse_reading() should turn a meter reading typed by a customer into a number.
Input that fails: "12,5" (customers in the Netherlands write a decimal comma).
Error:
ValueError: invalid literal for int() with base 10: '12,5'

Code:
def parse_reading(raw):
    return int(raw.strip())

Explain the cause in two sentences. Then show the smallest change that accepts
"12,5" and "12.5" as 12.5. Do not change anything else.

Two details in that prompt do a lot of work. "Explain the cause first" makes the model commit to a diagnosis you can judge before you look at any code. And "the smallest change" keeps the fix reviewable; without it, a debugging request often returns a rewritten function with three unrelated "improvements", one of which introduces a new bug.

Try it yourself

Open a new chat in an AI assistant (Claude, ChatGPT, Copilot, Gemini) and paste the first prompt. Then start another new chat and paste the second. A new chat keeps the first answer from influencing the second. The code is a small fictional example.

My script doesn't work, fix it.

from pathlib import Path

def newest_file(folder):
    files = [p for p in Path(folder).iterdir() if p.is_file()]
    return max(files, key=lambda p: p.stat().st_mtime)
Python 3.11 on Linux. This function should return the newest file in a folder, but it raises "ValueError: max() arg is an empty sequence" when the folder is empty.

Explain the cause in two sentences, then show a minimal fix that returns None for an empty folder. Do not change anything else.

from pathlib import Path

def newest_file(folder):
    files = [p for p in Path(folder).iterdir() if p.is_file()]
    return max(files, key=lambda p: p.stat().st_mtime)

What to look for: whether the first answer guesses the problem or fixes something else (the path type, symbolic links, performance); how much it changes that you did not ask for, such as type hints, logging, a command-line interface or a rewritten function; whether the second answer's fix touches only the empty case; and which of the two you could review and test in a minute. (The prompt quotes the error as Python 3.11 prints it; Python 3.12 and later word this as "max() iterable argument is empty".)

Asking for explanations

Explanations are one of the safest uses of an assistant: you change nothing, and you can test what you learn. Typical cases in IT work are an inherited script nobody documented, a dense regular expression, a long shell one-liner from a forum, or a SQL query that someone "just runs every month".

Explain only; do not change the code.

Below is a Bash script that runs every night on a Kestrel Energy server. I am a junior engineer who knows basic Bash.
1. Explain what it does in three sentences.
2. Then go through it line by line.
3. List anything that could delete or overwrite data, and under which conditions.
4. Say which parts you are unsure about, for example because they depend on files or variables not shown.

<script>
[the script, with hostnames and credentials removed]
</script>

Point 3 turns an explanation into a quick risk review, and point 4 gives the model room to say "this depends on something I cannot see" instead of guessing. Check what you learned by running the code, or a copy of it, on harmless test input. A wrong explanation is cheaper than wrong code, but only if you test it before you rely on it.

AI code review

AI code review is using a language model to examine a code change, typically a diff or a pull request, and report possible bugs, security problems, unclear code or missing tests, as an addition to review by people. A model is a tireless first reader: it does not skim the boring part of a large diff. It also lacks the context a colleague has: why the change was made, what the rest of the system expects, what the team agreed last month.

Review this change before I open a pull request.

Purpose of the change: add retry with backoff when the meter-reading API returns HTTP 503.
Focus on: error handling, whether the retries can loop forever, and anything that logs personal data.
Ignore: formatting and naming; a linter handles those.

For each finding, give the line, the problem, why it matters and a suggested fix. Order the findings by severity. If you find nothing serious, say so; do not invent findings to fill the list.

<diff>
[the diff]
</diff>

Stating the purpose lets the model check the change against what it is supposed to do. Focus areas keep it from burying one real problem under twenty style remarks. And permission to report nothing matters: asked to review, a model tends to find something, so an explicit "say so if there is nothing serious" reduces invented findings.

  • Check each finding. Some will be wrong, because the model misread the code or did not see the function that already handles the case.
  • Remember what it did not say. A clean AI review is not proof that the code is correct or secure. It does not replace a human code review, tests or security tooling such as code scanning.
  • Use it on your own code first. Reviewing your change before a colleague sees it saves their time, and you judge the findings with full context.

Vibe coding

Vibe coding is building software by describing what you want to an AI assistant and accepting the code it produces without reading it, steering only by whether the result seems to work. The AI researcher Andrej Karpathy coined the term in a post on X on 2 February 2025, and it spread quickly. The Vibe Coding topic covers it in depth, with a small game built step by step.

It has a reasonable place: a throwaway prototype on your own machine, a personal tool, an experiment to see whether an idea is worth building properly. It has no place where someone else depends on the result: production systems, scripts that run with administrator rights, anything that handles credentials or customer data, or code you will hand over to colleagues. The problem is not that the code was generated; it is that nobody knows what it does. "It seems to work" says nothing about the inputs you did not try, the errors it swallows or the files it writes. Once code leaves your sandbox, the rule from human accountability applies: do not commit or deploy what you could not explain.

Checking generated code

Generated code looks equally confident whether it is right or wrong, so check it the way you would check a new colleague's first pull request. The general rules are on reviewing generated code; the checks specific to prompting are these.

Dependencies the model suggests

Models sometimes recommend packages that do not exist, and attackers register such names to catch people who install them without checking (see package hallucination). A simple guard is to compare every package an answer asks you to install with a list your team has approved:

import re

recorded_answer = """\
Install the dependencies first:

    pip install requests python-dateutil
    pip install kestrel-meter-utils

Then run the script."""

APPROVED = {"python-dateutil", "requests"}

requested = []
for line in recorded_answer.splitlines():
    match = re.match(r"\s*pip install (.+)", line)
    if match:
        requested.extend(match.group(1).split())

for name in requested:
    status = "approved" if name.lower() in APPROVED else "NOT on the list: verify before installing"
    print(f"{name:<20} {status}")
requests             approved
python-dateutil      approved
kestrel-meter-utils  NOT on the list: verify before installing

"Verify" means: look the package up on the official package index, check who publishes it, how long it has existed and whether your team already uses something that does the job. Asking in the prompt for "standard library only" or "only these packages: …" avoids most of the problem.

Commands and coding agents

A chat assistant only shows you code; a coding agent can run it. Anthropic's prompting guide shows an instruction for agents to ask for confirmation before destructive or hard-to-reverse actions, such as deleting files, force-pushing, or dropping database tables. A prompt like that is a sensible habit, but it is not a safety mechanism: an instruction can be misread or overridden. Real limits come from the agent's permissions and the tool's approval settings (see guardrails).