Agent Skills in Claude Code and Visual Studio Code: Setup, Benefits, Risks, and a Step-by-Step Example

Image
 AI coding assistants are useful for one-off questions, but many development tasks are not one-off tasks. A team may follow the same testing procedure before every release, use a specific format for code reviews, or require certain checks whenever a database migration is created. Repeating those requirements in every prompt is inefficient. Putting all of them into a permanent instruction file is not always ideal either, because the assistant may receive irrelevant information for tasks that do not need it. This is the problem that Agent Skills are designed to solve. A skill packages reusable instructions, supporting files, examples, and optional scripts into a folder. Claude Code or GitHub Copilot in Visual Studio Code can discover that folder and load its contents when the current task matches the skill’s purpose. Both environments support the open Agent Skills format, which makes it possible to share some skills across tools. This guide explains what skills are, how they differ ...

A Beginner’s Guide to Debugging AI-Generated Code



AI coding assistants can generate code quickly, but they can also generate bugs quickly.

A feature may look correct at first. Then a button stops responding, a list disappears after refresh, a filter shows the wrong books, or the browser reports an error you do not understand.

The natural reaction is often to send another broad prompt:

Fix the app.

That request gives the AI very little evidence. It may guess at the cause, change several files, and introduce a second problem while trying to repair the first.

A better debugging process begins before asking the AI to edit anything. Reproduce the problem, record what happened, inspect the most recent change, identify one likely cause, and test one small correction.

This article shows how to debug AI-generated code without turning one bug into a chain of speculative changes.

What You Will Learn

By the end of this guide, you should be able to:

  • describe a bug in a reproducible way;
  • separate symptoms from likely causes;
  • collect browser and Git evidence;
  • inspect the most recent relevant change;
  • reduce a problem to one failing workflow;
  • write a useful debugging prompt;
  • test one hypothesis at a time;
  • recognize when an AI fix expands the scope;
  • verify that a fix does not create a regression;
  • restore a known working state when debugging goes off track.

Debugging Starts with Evidence

A bug report such as this is too vague:

The reading list is broken.

It does not explain:

  • what action failed;
  • what the user expected;
  • what actually happened;
  • whether an error appeared;
  • whether the problem happens every time;
  • which recent change may be related.

A useful bug report includes four basic parts:

Steps
Expected result
Actual result
Evidence

For example:

Steps:
1. Open the reading list app.
2. Add a book with the status "Reading."
3. Select the "Reading" filter.
4. Remove the visible book.

Expected result:
The book disappears and the filtered empty-state message appears.

Actual result:
The book disappears, but the page shows a blank list with no empty-state message.

Evidence:
No console error appears. The problem happens every time.

This description gives both you and the AI a clear starting point.

Symptoms Are Not Causes

A symptom is what you observe.

A cause is the underlying reason it happened.

For example:

Symptom:
The empty-state message does not appear after removing the final filtered book.

Possible causes include:

  • the remove function does not call the renderer;
  • the renderer checks the full list instead of the filtered list;
  • the active filter value is lost;
  • the empty-state element has the wrong selector;
  • a CSS rule hides the message;
  • the list is not actually empty.

Do not choose one cause immediately.

Debugging is the process of gathering enough evidence to eliminate incorrect explanations.

AI assistants can be helpful during that process, but they should not be asked to guess without context.

Step 1: Reproduce the Problem Consistently

Before changing code, confirm that the bug can be reproduced.

Write the exact sequence of actions.

For example:

1. Refresh the page.
2. Add "Kindred" with status "Finished."
3. Add "The Dispossessed" with status "Want to Read."
4. Select the Finished filter.
5. Remove "Kindred."
6. Observe the list area.

Repeat the sequence.

Ask:

  • Does the problem happen every time?
  • Does it happen only after refresh?
  • Does it happen only with one status?
  • Does it happen only when the list contains one item?
  • Does it happen in another browser?
  • Does it happen with the All filter?

A reproducible bug is easier to investigate than an intermittent one.

Reduce the Steps

Remove unnecessary actions.

If the problem happens with one book, do not include five books in the reproduction steps.

A minimal reproduction could be:

1. Add one Finished book.
2. Select the Finished filter.
3. Remove the book.
4. The filtered empty state does not appear.

The smaller the reproduction, the fewer possible causes you need to consider.

Step 2: Confirm the Starting Git State

Before debugging, run:

git status

You need to know whether the working tree already contains uncommitted changes.

A clean state may look like:

nothing to commit, working tree clean

A dirty state may show:

modified: app.js
modified: styles.css

When uncommitted changes are already present, determine whether they belong to:

  • the bug;
  • unfinished work;
  • an earlier AI request;
  • an unrelated experiment.

Mixing a bug fix with unknown existing changes makes debugging harder.

Also inspect recent commits:

git log --oneline -5

The most recent relevant commit may reveal when the behavior changed.

For example:

4f2a0bc Add book removal
d8be113 Filter books by reading status
a4c76e9 Validate book titles

If the bug appeared after adding removal, start by reviewing that change.

Step 3: Check the Browser Console

Open the browser’s developer tools and select the Console panel.

Then reproduce the problem again.

A JavaScript error may look like:

Uncaught TypeError: Cannot read properties of null

Or:

ReferenceError: activeFilter is not defined

Record:

  • the full error message;
  • the file name;
  • the line number;
  • the user action that triggered it.

Do not copy only part of the message.

A useful note might be:

Error:
ReferenceError: activeFilter is not defined

Location:
app.js:87

Triggered by:
Removing the final book while the Finished filter is active

This is much more useful than:

There is a JavaScript error.

No Console Error Does Not Mean No Bug

Logic errors may not produce an exception.

The application can execute successfully while displaying the wrong result.

For example, this condition may be valid JavaScript but incorrect for filtered data:

emptyState.hidden = books.length > 0;

If the full books array contains items from other statuses, the empty state remains hidden even when the filtered result is empty.

The browser does not report an error because the code is syntactically valid.

You must compare behavior with the requirement.

Step 4: Inspect the Relevant Git Diff

When the bug appears after a recent change, inspect that commit.

For the latest commit:

git show --stat
git show

For a specific commit:

git show 4f2a0bc

Suppose the removal commit added:

function removeBook(bookId) {
  books = books.filter((book) => book.id !== bookId);
  saveBooks();
  renderBooks();
}

The function looks reasonable.

Now inspect renderBooks():

function renderBooks() {
  const visibleBooks = getFilteredBooks();

  bookList.replaceChildren();

  visibleBooks.forEach((book) => {
    bookList.append(createBookElement(book));
  });

  emptyState.hidden = books.length > 0;
}

The likely problem is the final line.

It checks the total number of books rather than the number of currently visible books.

That creates a testable hypothesis.

Step 5: Form One Specific Hypothesis

A useful debugging hypothesis is narrow and testable.

For example:

The empty state remains hidden because renderBooks() checks books.length instead of visibleBooks.length.

This is better than:

The rendering code is bad.

A good hypothesis identifies:

  • the suspected code;
  • the expected relationship;
  • the observable result.

You can now test it with a temporary inspection.

For example:

console.log({
  totalBooks: books.length,
  visibleBooks: visibleBooks.length
});

Reproduce the bug.

The console may show:

totalBooks: 1
visibleBooks: 0

That evidence supports the hypothesis.

Remove temporary debugging statements before committing.

Step 6: Make the Smallest Possible Fix

The correction may be one line:

- emptyState.hidden = books.length > 0;
+ emptyState.hidden = visibleBooks.length > 0;

Do not redesign the renderer, rename all functions, or add a framework to fix one condition.

A focused bug fix should change only what is necessary to correct the failing behavior.

After the edit, repeat the exact reproduction steps.

Confirm that the empty state now appears.

Then test related behavior:

  • All filter with books;
  • All filter with no books;
  • Finished filter with one matching book;
  • Finished filter with no matching books;
  • another status with visible books.

This checks both the fix and nearby behavior.

Step 7: Use the Elements Panel for Interface Problems

Some bugs are caused by HTML or CSS rather than JavaScript.

Suppose the validation message exists but cannot be seen.

Use the Elements panel in the browser’s developer tools.

Check:

  • Is the element present?
  • Does it contain text?
  • Is it hidden with the hidden attribute?
  • Does CSS set display: none?
  • Is the text color too similar to the background?
  • Is the element outside the visible layout?
  • Does another element cover it?

For example:

<p id="title-error" class="error-message" hidden></p>

The JavaScript may add text:

titleError.textContent = "Please enter a book title.";

But it may forget to remove the hidden attribute:

titleError.hidden = false;

The problem is not that validation failed.

The problem is that the feedback remained hidden.

Step 8: Inspect Values at Important Points

When code produces the wrong result, inspect the values moving through it.

Useful checkpoints include:

  • form input values;
  • the new book object;
  • the books array;
  • the active filter;
  • the filtered result;
  • local storage content.

For example:

console.log("Submitted title:", title);
console.log("Selected status:", status);
console.log("Current books:", books);

Use logging temporarily and intentionally.

Avoid adding many logs without a question.

Weak debugging:

console.log("here");
console.log("still here");
console.log("test");

Better debugging:

console.log({
  activeFilter,
  totalBookCount: books.length,
  visibleBookCount: visibleBooks.length
});

The second version provides evidence connected to a hypothesis.

Step 9: Check DOM Selectors

A common beginner bug occurs when JavaScript looks for an element that does not exist or uses the wrong ID.

HTML:

<select id="status-filter">

JavaScript:

const statusFilter = document.querySelector("#book-filter");

The selector returns null because the IDs do not match.

Later code may fail:

statusFilter.addEventListener("change", handleFilterChange);

The console may report:

TypeError: Cannot read properties of null

Review the selector and HTML side by side.

Check for:

  • spelling differences;
  • renamed IDs;
  • duplicated IDs;
  • scripts loading before the HTML exists;
  • selectors pointing to old markup.

This is a frequent problem after AI edits because one file may be updated while another retains the previous name.

Step 10: Check Data Shape Consistency

The application expects each book to have:

id
title
author
status
createdAt

A bug can occur when one code path creates a different object.

For example:

const book = {
  id: crypto.randomUUID(),
  name: title,
  writer: author,
  readingStatus: status
};

The renderer may expect:

titleElement.textContent = book.title;

The displayed title becomes empty because book.title does not exist.

Inspect the object when it is created:

console.log(book);

Compare it with the project brief and other functions.

Data bugs often come from inconsistent names rather than complicated algorithms.

Step 11: Debug Local Storage Carefully

Persistence adds another possible source of problems.

Suppose books disappear after refresh.

Check whether the app saves data:

localStorage.setItem(STORAGE_KEY, JSON.stringify(books));

Then check whether it loads data:

const savedBooks = localStorage.getItem(STORAGE_KEY);

Review:

  • Is the same storage key used for saving and loading?
  • Is the value converted to JSON before saving?
  • Is it parsed after loading?
  • Does initialization overwrite the loaded array?
  • Does removal save the updated array?
  • Does invalid JSON cause the entire script to stop?

A key mismatch may look like:

const STORAGE_KEY = "reading-list";

localStorage.setItem("readingList", JSON.stringify(books));

Saving uses readingList, while loading uses reading-list.

Both lines are valid, but they refer to different entries.

Inspect Stored Data

In browser developer tools, open the storage or application panel.

Find the site’s local storage and inspect the stored value.

You may see:

[
  {
    "id": "book-001",
    "title": "Kindred",
    "author": "Octavia E. Butler",
    "status": "finished",
    "createdAt": "2026-07-11T10:30:00.000Z"
  }
]

If the stored value is missing, investigate saving.

If the value exists but the interface is empty, investigate loading or rendering.

This distinction prevents unnecessary changes to the wrong part of the code.

Step 12: Check Event Handling

A button that appears unresponsive may have an event problem.

Review:

  • Is the event listener attached?
  • Is the correct event type used?
  • Does the handler run?
  • Does form submission reload the page?
  • Does a button have the correct type?
  • Is event delegation using the right selector?

For form submission:

function handleBookSubmit(event) {
  event.preventDefault();
  // ...
}

Without event.preventDefault(), the browser may reload the page and clear the in-memory list.

For a remove button inside a form, the HTML should often include:

<button type="button">Remove</button>

Without type="button", a button may behave as a submit button by default.

That can create a confusing bug where clicking Remove also triggers form submission.

Step 13: Write a Better Debugging Prompt

Once you have evidence, give the AI a focused debugging request.

Weak prompt:

Fix the filtering bug.

Improved prompt:

Investigate a bug in the Personal Reading List Tracker.

Steps to reproduce:
1. Add one book with status Finished.
2. Add one book with status Want to Read.
3. Select the Finished filter.
4. Remove the Finished book.

Expected result:
The filtered empty-state message appears.

Actual result:
The list is blank, but the empty-state message remains hidden.

Evidence:
- No console error appears.
- books.length is 1.
- visibleBooks.length is 0.
- The bug appears after the "Add book removal" commit.

Constraints:
- Inspect app.js only.
- Do not edit yet.
- Identify the most likely cause and explain the smallest fix.
- Do not change the data model, storage logic, styling, or HTML.

After reviewing the diagnosis:

Apply only the proposed empty-state fix in app.js. Then provide the exact manual tests needed to verify filtered and unfiltered empty states.

This prompt gives the AI evidence and a narrow boundary.

Step 14: Do Not Accept Multiple Speculative Fixes

An AI assistant may suggest several changes at once:

- Rewrite renderBooks
- Rename the filter state
- Replace event listeners
- Add a new utility module
- Change the HTML structure

That may be excessive for one reproducible bug.

Ask:

  • Which change directly addresses the evidence?
  • Can the hypothesis be tested with one line or one function?
  • Which proposed changes are unrelated cleanup?
  • Could the bug be fixed without changing the architecture?

Apply one justified fix first.

If it fails, restore or revise it before trying another theory.

Step 15: Verify the Fix Against the Original Bug

After applying a fix, repeat the same steps that reproduced the problem.

Do not change the test sequence.

For example:

1. Add one Finished book.
2. Add one Want to Read book.
3. Select Finished.
4. Remove the Finished book.
5. Confirm the filtered empty state appears.

Then test neighboring cases:

- Select All and confirm the remaining book appears.
- Select Want to Read and confirm the remaining book appears.
- Remove the final book and confirm the global empty state appears.
- Add another book and confirm the empty state disappears.

A bug is not fixed merely because the code changed.

The original failing behavior must now pass.

Step 16: Check for Regressions

A regression occurs when a fix breaks something that previously worked.

Suppose changing the empty-state condition fixes filtered results but causes the initial empty state to disappear.

Test:

  • initial page with no books;
  • page with one book;
  • filter with one match;
  • filter with no matches;
  • removal of the last visible item;
  • removal of the final stored item.

The exact regression tests depend on the changed behavior.

A useful rule is:

Test the bug
+ Test the normal path
+ Test one related edge case

For more important changes, add automated tests when the project supports them.

Step 17: Review the Final Diff

After the fix works, run:

git status
git diff

A focused fix may show only:

modified: app.js

Review for:

  • temporary console.log statements;
  • commented-out experiments;
  • unrelated formatting;
  • additional files;
  • changed packages;
  • duplicated code;
  • accidental data.

Remove debugging leftovers unless they serve a deliberate long-term purpose.

Then stage the intended file:

git add app.js
git diff --staged

Create a commit message that describes the corrected behavior:

git commit -m "Show empty state for filtered book lists"

A bug-fix commit should make the change easy to identify later.

Common Debugging Mistakes

Asking the AI to Fix Everything

A broad request encourages broad edits.

Provide one reproducible bug and one review boundary.

Changing Code Before Reproducing the Problem

Without a reliable reproduction, you cannot prove that the change fixed anything.

Testing Several Hypotheses at Once

If multiple changes are applied together, you do not know which one mattered.

Ignoring the Console

A full error message and line number can save considerable time.

Ignoring Git History

The most recent relevant commit often narrows the search.

Leaving Temporary Logs

Debugging output can clutter the console or expose data.

Review the final diff before committing.

Trusting the AI’s “Fixed” Summary

Repeat the failing steps yourself.

The summary is not a test result.

Rewriting the Entire Feature

A small bug usually deserves a small fix.

A rewrite may introduce more uncertainty than it removes.

Debugging from Memory

Record the exact steps, values, and errors.

Human memory and AI conversation context can both drift.

A Practical Debugging Worksheet

Use this structure when investigating a problem.

Bug title:
[Short description]

Environment:
[Browser, local project, current branch]

Starting Git state:
[Clean or list existing changes]

Steps to reproduce:
1.
2.
3.

Expected result:
[What should happen]

Actual result:
[What happens instead]

Console evidence:
[Full error and line number, or "no console error"]

Relevant recent change:
[Commit or file]

Observed values:
[Important state, inputs, or stored data]

Current hypothesis:
[One specific suspected cause]

Smallest proposed test:
[Temporary log, condition check, or isolated edit]

Fix applied:
[Exact small change]

Regression checks:
[Related behaviors tested]

Final result:
[Pass or fail]

This worksheet is useful whether you debug alone or with an AI assistant.

Example: Books Return After Removal

Consider another common bug.

Symptom

A removed book disappears immediately but returns after page refresh.

Reproduction

1. Add a book.
2. Remove it.
3. Refresh the page.
4. The removed book returns.

Evidence

The browser console shows no error.

Local storage still contains the removed book.

Likely Cause

The removal function updates the in-memory array but does not save the new array.

Current code:

function removeBook(bookId) {
  books = books.filter((book) => book.id !== bookId);
  renderBooks();
}

Smallest Fix

function removeBook(bookId) {
  books = books.filter((book) => book.id !== bookId);
  saveBooks();
  renderBooks();
}

Verification

Test:

  • remove one of two books;
  • refresh;
  • confirm only the remaining book returns;
  • remove the last book;
  • refresh;
  • confirm the list remains empty.

The debugging process connects the symptom to stored evidence before changing the code.

Example: Form Submission Adds Two Books

Symptom

One form submission adds the same book twice.

Possible Causes

  • the event listener was attached twice;
  • both a click handler and submit handler add the book;
  • the script file is included twice;
  • the rendering function duplicates existing items;
  • the form submits once and another function repeats the operation.

Evidence to Collect

Check:

console.count("handleBookSubmit called");

Submit once.

If the count increases by two, the handler may be registered twice.

Inspect index.html for duplicate scripts:

<script src="app.js"></script>
<script src="app.js"></script>

Or inspect JavaScript for duplicate listeners:

bookForm.addEventListener("submit", handleBookSubmit);
bookForm.addEventListener("submit", handleBookSubmit);

Do not add a duplicate-prevention condition until you understand why the handler runs twice.

Fix the cause rather than hiding the symptom.

When to Start a Fresh AI Session

A long debugging conversation may accumulate incorrect assumptions.

Start a fresh session when:

  • the AI repeatedly returns to a disproven hypothesis;
  • the tool edits unrelated files;
  • the conversation refers to old code;
  • several speculative fixes have failed;
  • the current diff is difficult to explain;
  • the assistant no longer respects the project brief.

A fresh debugging prompt should include:

  • the current project state;
  • one reproducible bug;
  • the exact error;
  • relevant file names;
  • recent Git changes;
  • current evidence;
  • explicit file boundaries.

Do not carry every failed theory into the new session unless it helps eliminate a cause.

When to Revert Instead of Continuing

Sometimes the safest debugging decision is to return to the last working version.

Consider reverting when:

  • the feature is small but the fix is becoming large;
  • several unrelated files have changed;
  • the original behavior is no longer clear;
  • each repair creates another error;
  • the tool introduced an unexplained dependency;
  • the current code is harder to understand than the previous version.

Inspect first:

git status
git diff

To discard uncommitted changes in one tracked file:

git restore app.js

To reverse a committed change safely in shared history:

git revert <commit-hash>

Do not use destructive reset commands casually.

A clean restart from a known checkpoint can be faster than continuing an unstable chain of patches.

Practical Advice from Real Debugging Work

In software development, the visible failure is often far from the actual cause.

A missing item on the screen may come from:

  • incorrect input;
  • inconsistent data;
  • a failed storage operation;
  • a filter condition;
  • a rendering decision;
  • a CSS rule.

Strong debugging narrows the system one boundary at a time.

When working with AI-generated code, I prefer prompts that ask the tool to explain the evidence before editing. This makes it easier to see whether the proposed cause matches the actual repository.

The most reliable debugging loop is:

Reproduce
→ Observe
→ Hypothesize
→ Test
→ Fix
→ Verify
→ Commit

Skipping directly from symptom to generated patch removes the most important reasoning steps.

A Reusable Debugging Checklist

Reproduce

  • Write exact steps.
  • Reduce the steps to the smallest failing sequence.
  • Confirm whether the problem happens consistently.
  • Record the expected and actual results.

Gather Evidence

  • Run git status.
  • Review recent commits.
  • Check the browser console.
  • Record the full error and line number.
  • Inspect relevant DOM elements.
  • Inspect important state values.
  • Check local storage when persistence is involved.

Form a Hypothesis

  • Identify one likely cause.
  • Connect it to specific evidence.
  • Define one small test.
  • Avoid changing several systems at once.

Apply a Fix

  • Change the smallest necessary area.
  • Keep file scope narrow.
  • Reject unrelated refactoring.
  • Do not add dependencies without a clear reason.

Verify

  • Repeat the original reproduction steps.
  • Test the normal path.
  • Test one related edge case.
  • Check for regressions.
  • Remove temporary debugging output.

Commit

  • Review git diff.
  • Stage specific files.
  • Review git diff --staged.
  • Use a commit message that describes the corrected behavior.

Conclusion

Debugging AI-generated code is not about sending repeated “fix it” prompts.

It is a structured process of reducing uncertainty.

Start by reproducing the problem. Record the expected and actual behavior. Check the browser console, inspect recent Git changes, trace relevant values, and form one specific hypothesis. Apply the smallest justified fix, repeat the original test, check for regressions, and review the final diff before committing.

The AI assistant can help explain code, identify likely causes, and propose a correction. Your evidence determines whether that correction is credible.

The next article will focus on what to do when an entire AI coding session goes off track. We will examine context drift, uncontrolled scope expansion, contradictory changes, and how to recover without losing verified work.

Frequently Asked Questions

Should I paste the full project into an AI debugging prompt?

Usually not.

Provide the relevant files, exact reproduction steps, error message, recent changes, and constraints. A project-level coding agent can inspect the repository, but you should still define a narrow problem.

What should I do when there is no console error?

Treat it as a logic or interface problem.

Compare actual behavior with the acceptance criteria, inspect important state values, review the relevant condition, and use the Elements panel to check whether the interface is hidden or incorrectly updated.

How many fixes should I try before reverting?

There is no fixed number.

Revert when the current state becomes harder to understand, the scope keeps expanding, or each speculative fix creates another issue. A known working checkpoint is often the best place to restart.

Comments

Popular posts from this blog

Claude Code Multi-Agent Setup for Beginners: Models, Roles, and Your First Project

The Limits and Future of Vibe Coding

How to Test and Deploy Your First Vibe-Coded App