Prompting for Debugging
Why Debugging Prompts Are Different
Debugging is the task where prompt quality matters most. When generating code, a mediocre prompt still gives you something to work with. When debugging, a vague prompt gives you the wrong diagnosis -- and the wrong fix can introduce new bugs while masking the original one.
The fundamental challenge with debugging prompts is that you are describing a gap between expected and actual behavior. The AI needs to understand both sides of that gap, plus the code that connects them, to identify the root cause. Leave out any piece and the AI will guess -- and guessing at the cause of a bug is how you spend hours chasing the wrong problem.
The Debugging Prompt Template
Every effective debugging prompt includes five pieces of information:
1. ACTUAL BEHAVIOR: What happens right now (the bug)
2. EXPECTED BEHAVIOR: What should happen instead
3. STEPS TO REPRODUCE: How to trigger the bug
4. ERROR MESSAGE: The exact error text, stack trace, or log output
5. RELEVANT CODE CONTEXT: The code involved in the bug
Let us see each component in action.
Actual Behavior
Bad:
The app is broken
Good:
When a user submits the registration form with a valid email and password,
the API returns a 500 status code instead of creating the user account.
Be specific about what happens: does the page crash? Does it show an error? Does it hang? Does it return wrong data? Does it silently fail?
Expected Behavior
Bad:
It should work
Good:
The API should create a new user record in the database, hash the password
with bcrypt, and return a 201 status code with the user object (without the
password field).
Defining expected behavior helps the AI understand your intent. Sometimes the "bug" is actually the code working as designed but not matching your expectations -- the AI needs to know your expectations to distinguish between the two.
Steps to Reproduce
Steps to reproduce:
1. Navigate to /register
2. Enter email: test@example.com, password: "ValidPass123!"
3. Click "Create Account"
4. Observe the network tab: POST /api/auth/register returns 500
This happens with every new email. Existing emails correctly return 409.
Reproduction steps help the AI trace the exact execution path. Including what works (existing emails return 409) is as important as what does not work because it narrows down where the bug lives.
Error Message
Always include the exact error text. Copy-paste it -- do not paraphrase.
Bad:
It shows some kind of database error
Good:
Error from server logs:
PrismaClientKnownRequestError:
Invalid `prisma.user.create()` invocation:
The required column `user.email_verified` has no default value, and no value
was provided in the create data.
at src/routes/auth.ts:47:28
at processTicksAndRejections (node:internal/process/task_queues:95:5)
The exact error message tells the AI precisely what went wrong. In this case, the AI can immediately identify that the email_verified column is required but not being set in the create call -- a one-line fix.
Relevant Code Context
Provide the code that is involved in the bug. Do not dump the entire file -- focus on the relevant function and its immediate dependencies.
Here is the registration handler (src/routes/auth.ts, lines 35-55):
async function register(req: Request, res: Response) {
const { email, password } = req.body;
const hashedPassword = await bcrypt.hash(password, 10);
const user = await prisma.user.create({
data: {
email,
passwordHash: hashedPassword,
},
});
res.status(201).json({ id: user.id, email: user.email });
}
And here is the Prisma schema for User:
model User {
id String @id @default(uuid())
email String @unique
passwordHash String
emailVerified Boolean
createdAt DateTime @default(now())
}
With all five components, the AI can immediately see that emailVerified is a required Boolean with no default value, and the create call does not include it. Fix: add emailVerified: false to the create data or add @default(false) to the schema.
Putting It All Together
Here is a complete debugging prompt that combines all five components:
Bug: Registration endpoint returns 500 for new users.
Actual behavior: POST /api/auth/register returns 500 with a Prisma error
when creating a new user with a valid email and password.
Expected behavior: Should create the user and return 201 with the user object.
Steps to reproduce: POST to /api/auth/register with body
{ "email": "new@test.com", "password": "ValidPass123!" }. Happens for every
new email. Existing email correctly returns 409 Conflict.
Error message:
PrismaClientKnownRequestError: Invalid `prisma.user.create()` invocation:
The required column `user.email_verified` has no default value.
at src/routes/auth.ts:47:28
Relevant code: The register handler in src/routes/auth.ts creates a user
with only email and passwordHash fields, but the Prisma schema requires
emailVerified (Boolean with no default).
Please fix this bug and explain the root cause.
The Narrow Scope Technique
One of the most valuable debugging techniques is narrowing the scope of your description. The wider the scope, the more the AI has to search. The narrower the scope, the more targeted the fix.
Wide scope (slow, often wrong):
The authentication system is not working properly. Users are having trouble
logging in.
Narrow scope (fast, accurate):
The login form in src/pages/Login.tsx submits successfully (POST /api/auth/login
returns 200 with a valid JWT), but the useAuth() hook in AuthContext does not
update isAuthenticated to true. The router.push('/dashboard') on line 42 of
Login.tsx fires before the state update completes, so the dashboard route's
auth guard redirects back to login. This creates an infinite redirect loop
between /login and /dashboard.
The narrow scope prompt gives the AI a complete picture of the bug chain. It will immediately know to look at the async state update timing, not the JWT validation, API response, or form submission.
Common Debugging Prompt Patterns
Pattern 1: Explain This Error
Explain this error in the context of my Express + Prisma + TypeScript project:
TypeError: Cannot read properties of undefined (reading 'id')
at getUserOrders (src/routes/orders.ts:23:35)
at Layer.handle (node_modules/express/lib/router/layer.js:95:5)
The function getUserOrders tries to access req.user.id, but req.user is
undefined. The authMiddleware should attach the user object to req. Here is
the middleware code: [paste middleware]. Here is the route registration: [paste
route setup].
Pattern 2: Why Does X Return Y Instead of Z
In src/utils/dateFormatter.ts, the function formatRelativeDate('2026-03-25')
returns "in 0 days" instead of "yesterday" when called on March 26, 2026.
Here is the function:
[paste function code]
I expect it to return "yesterday" for dates exactly one day in the past. It
seems like the day difference calculation is rounding down instead of using
the calendar day boundary.
Pattern 3: Trace the Data Flow
Trace the data flow for a user's order from creation to display:
1. User clicks "Place Order" in src/components/Checkout.tsx
2. This calls createOrder() in src/services/orderService.ts
3. Which POSTs to /api/orders
4. Handler in src/routes/orders.ts creates the order with Prisma
5. Response returns to the frontend
6. Order should appear in src/pages/OrderHistory.tsx
The order is created in the database (I can see it in psql), but it does not
appear on the OrderHistory page. Where in this chain is the data getting lost?
[paste relevant code for the order list query and the component]
Including Environment Context
Sometimes bugs are environment-specific. When you suspect this, include the environment details:
This bug only happens in production, not in local development.
Environment:
- Node.js 20.11.0 (production), 20.10.0 (local)
- PostgreSQL 15 on AWS RDS (production), PostgreSQL 15 local Docker
- Running behind AWS ALB with SSL termination
- Environment variables are set via AWS Secrets Manager
The API returns correct data locally but returns empty arrays in production
for the same database query. The database has data (verified with direct
psql connection).
I suspect the database connection string or SSL configuration might differ.
Here is our database connection setup: [paste code]
Anti-Pattern: The Log Dump
Do not paste 200 lines of logs and say "find the bug." The AI will get lost in irrelevant information and may focus on warnings or noise instead of the actual error.
Bad:
Here are the logs, find what is wrong:
[200 lines of mixed info, warning, and error logs]
Good:
Here is the relevant error from the logs (filtered from the full output):
[ERROR] 2026-03-26T10:15:32Z OrderService.createOrder: Foreign key constraint
failed on field `customerId`. Customer ID "cust_abc123" does not exist in the
customers table.
This error occurs when processing orders from our webhook handler. The
webhook receives events from Stripe, and the customerId comes from the Stripe
customer metadata. I think the issue is that we are not creating the customer
record before processing the first order.
Filter your logs. Highlight the relevant error. Provide your hypothesis. This turns a 10-minute investigation into a 30-second fix.
The Hypothesis Technique
When you have a theory about what is wrong, include it. Even if your hypothesis is wrong, it gives the AI a starting point and shows it where you have already looked.
Bug: The WebSocket connection drops every 60 seconds.
My hypothesis: Our Nginx reverse proxy has a default proxy_read_timeout of
60 seconds, and WebSocket frames are not being sent frequently enough to keep
the connection alive. I think we need either a ping/pong heartbeat or an
increased timeout.
Relevant config: [paste Nginx config]
WebSocket setup: [paste server-side WS code]
Is my hypothesis correct? If not, what else could cause exactly 60-second
disconnects?
This saves the AI from exploring dead ends and focuses the analysis on likely causes. And if your hypothesis is wrong, the AI will explain why and suggest alternatives.