Loop Engineering with Claude Code

Build an autonomous bug-fix loop on Flask using Claude Code skills and hooks.

Introduction

30 Second Summary

You give an AI coding agent a task, it produces a fix, and you cross your fingers that it actually works. Without independent verification, you are the one manually checking every change. That is the bottleneck loop engineering eliminates.

In this project, you will build a complete loop engineering system on a real open-source codebase. You will wire up Claude Code's primitives into a loop that autonomously discovers a bug, fixes it, verifies the fix with an independent checker, and commits the result.

What You'll Build

You set a goal condition, walk away, and come back to find Claude has iterated through multiple turns of discovery, fixing, and verification until the test suite passes and the fix is committed to Flask.

By the end of this project, you'll have:

  • A custom skill you can invoke to instantly triage failing tests and output a structured diagnosis of the Flask codebase.
  • A PostToolUse hook that automatically runs tests after every file edit, catching regressions the moment they happen without you or Claude choosing to check.
  • A CLAUDE.md spine that remembers what was tried and what passed across sessions, combined with a /goal-driven autonomous session that iterates until all tests pass and the fix is committed.
  • Secret Mission: Add a scheduled /loop monitor that continuously watches the test suite on a cadence and triages new failures automatically.

Are there any prerequisites?

This project assumes you already have Claude Code installed and authenticated, a GitHub account, and Python 3 on your machine. You do not need prior experience with loop engineering or Flask's internals.

Before We Start

Before we dive in, take a moment to commit to what you're about to build and why it matters for how you work with AI coding agents.

Fork Flask and Set Up the Development Environment

Loop engineering only works when there's a real test suite providing ground truth. A toy project with a handful of tests would not expose the verification challenges that make the maker/checker pattern valuable.

Flask is a popular open-source web framework with a well-structured pytest suite. In this step, you'll fork the repository, set up a local development environment, and confirm the tests run.

In this step, get ready to:
  • Fork Flask and clone the repository locally.
  • Install development dependencies and confirm pytest works.
  • Create a feature branch for your loop engineering work.
Fork and clone the Flask repository

Forking creates your own copy of Flask on GitHub. You'll clone that fork locally so you have a codebase to wire your loop engineering system against.

  • Sign in to your GitHub account at github.com.
  • Navigate to the Flask repository at github.com/pallets/flask.
  • Click the Fork button in the top-right corner of the page.
  • Keep the default repository name flask and click Create fork.

GitHub redirects you to your new fork. The page header now reads your username followed by /flask.

What is a fork?

A fork is a full copy of a repository under your own GitHub account. You can push commits to it freely without affecting the original project.

  • Open your terminal. Press Cmd+Space to open Spotlight, type Terminal, and press Enter.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Clone your fork by running this command:
git clone https://github.com/[[GITHUB_USERNAME="your-github-username"]]/flask.git

Git downloads the repository. Once it finishes, you'll see a flask folder on your Desktop.

Permission denied or authentication error?

If Git prompts for credentials or shows a permission error, confirm you're signed in to GitHub in your terminal. You may need to set up a personal access token or SSH key.

Help me fix Git authentication for cloning my fork.

  • Move into the Flask directory and create a virtual environment by running these commands:
cd flask
python3 -m venv .venv
source .venv/bin/activate

Your terminal prompt now shows (.venv) at the beginning, confirming the virtual environment is active.

The flask repo is now updated and the requirements file doesn't exist any so please follow the steps below to make sure everything works right.

Step 1: Create the "requirements" directory

Make sure you are in your flask directory, then create the missing folder:


mkdir requirements
Step 2: Create the dev.txt file

Create the empty file inside that folder:


touch requirements/dev.txt
Step 3: Edit the file and add dependencies

Open the file in your preferred text editor (like VS Code, nano, or TextEdit) and paste these package dependencies. If you are using nano in the terminal, run:


nano requirements/dev.txt

Paste the following contents inside requirements/dev.txt:


pytest
asgiref
python-dotenv

(If you are using nano, save and exit by pressing Ctrl + O, then Enter, then Ctrl + X.)

Step 4: Run the project commands again

Now that the directory and file are in place, the original installation commands will run without any errors!


pip install -r requirements/dev.txt
pip install -e .

Once you've done this, the environment setup is fully aligned with the project steps! You can run pytest tests/test_cli.py -x -q to verify everything is operating properly.

  • Install Flask's development dependencies by running these commands:
python -m pip install -U pip
pip install -r requirements/dev.txt
pip install -e .

What do these commands do?

  • python -m pip install -U pip updates pip to the latest version.
  • pip install -r requirements/dev.txt installs everything Flask needs for development and testing, including pytest.
  • pip install -e . installs Flask itself in editable mode so test imports resolve correctly.

The install prints a stream of package downloads. When it finishes, you'll see a line confirming the packages were successfully installed.

Install failing with a build error?

Make sure you activated the virtual environment first. Run which python and confirm it points to a path inside .venv/. If it points to your system Python, re-run source .venv/bin/activate and try the install again.

Help me fix the dependency install for Flask's dev environment.

Run a subset of Flask's test suite

With dependencies installed, you can confirm the test suite works by running a focused subset of tests. Flask's CLI tests are fast and self-contained, making them ideal for quick verification.

Before you run this, what do you expect to see? Will all CLI tests pass on a fresh clone of Flask, or might some fail?

  • Run the CLI test subset by running this command:
pytest tests/test_cli.py -x -q

✔️ I see test results printed

Whether you see all tests passing or a few failures, the important thing is that pytest executed and printed results. Your development environment is working correctly.

ⓧ I see an import error or command not found

  • If you see command not found: pytest, your virtual environment is not active. Run source .venv/bin/activate and try again.
  • If you see a ModuleNotFoundError, the dev dependencies did not install fully. Re-run pip install -r requirements/dev.txt and check for errors in the output.

Help me get pytest running in my Flask dev environment.

What do the flags mean?

  • -x stops at the first failure, keeping output short.
  • -q enables quiet mode, printing only pass/fail dots and the summary line.
Create a feature branch for your loop engineering work

Isolating your changes on a dedicated branch keeps the main branch clean. If something goes wrong, you can always start fresh.

  • Create and switch to your feature branch by running this command:
git checkout -b loop-engineering

Git prints Switched to a new branch 'loop-engineering'. You're now working on an isolated branch.

Your development environment is ready with a working test suite on an isolated branch. Next up, you'll create the CLAUDE.md spine and experience firsthand why a single-prompt approach falls short without independent verification.

Write a CLAUDE.md Spine and Try a Naive Fix

Your development environment is ready with pytest confirmed working on the Flask fork. Now you need to answer a fundamental question: can Claude fix a bug reliably with just a single prompt and no structure?

Before building anything complex, you will try the simplest possible approach and observe what happens. A CLAUDE.md file acts as the project's persistent memory, remembering what has been tried and what passed or failed across sessions. You will create that spine, then give Claude Code a single naive prompt to fix a test failure.

In this step, get ready to:
  • Create a CLAUDE.md spine that persists project state across sessions.
  • Try a naive single-prompt fix with no verification structure.
  • Run the test suite and observe the limitation firsthand.
Create the CLAUDE.md spine

In loop engineering, the spine is a persistent state file that remembers what got tried, what passed, and what failed. This way, the next session picks up where today stopped instead of starting from scratch.

  • Create a file called CLAUDE.md at the root of your Flask repository by running:
touch CLAUDE.md
  • Open CLAUDE.md in your text editor and paste the following content:
# Flask Loop Engineering

## Project Context

This is a fork of pallets/flask used to practice loop engineering.
The goal is to autonomously discover, fix, and verify bugs using
Claude Code primitives (skills, hooks, /goal).

## Coding Standards

- Follow Flask's existing code style and conventions.
- Match the formatting patterns in surrounding code.
- Write tests that follow Flask's existing test patterns.
- Keep commits atomic with descriptive messages.

## State

### Attempts Log

(No attempts recorded yet.)

What does this file do?

Claude Code reads CLAUDE.md at the start of every session. It loads the project context, coding standards, and state so Claude has persistent memory across conversations.

The State section is the key piece for loop engineering. After each attempt, you (or Claude) update it with what was tried and what happened. Future sessions see this history and avoid repeating failed approaches.

  • Save CLAUDE.md.
  • Confirm the file exists by running:
cat CLAUDE.md

You should see the full contents of your spine file printed in the terminal.

Not seeing the file contents?

  • Make sure you are in the root of your Flask repository, not a subdirectory.
  • Check that the file name is exactly CLAUDE.md (all caps for CLAUDE, lowercase for .md).

help me find where CLAUDE.md should go in my Flask project

Try a naive single-prompt fix in Claude Code

Now you will open Claude Code and try the simplest possible approach: one prompt, no skill, no hook, no goal. This is how most people first use an AI coding agent.

  • Launch Claude Code in your terminal by running:
claude

What is Claude Code?

Claude Code is a terminal-based AI coding agent. It reads your project files, runs commands, and edits code directly from the command line. It automatically reads your CLAUDE.md at session start.

Claude Code opens an interactive prompt where you can type messages. You are going to give it a single naive instruction to find and fix a test failure.

  • At the Claude Code prompt, type the following and press Enter:
Run pytest tests/test_cli.py -x -q. If any test fails, fix it. If all tests pass, introduce a small bug in src/flask/cli.py that causes exactly one test in tests/test_cli.py to fail, then fix it. Do not ask me any questions.
  • Watch Claude work through the task. Allow any tool permissions it requests (read, edit, bash).
  • Once Claude reports that it has finished, pay attention to what just happened in its process.

What should I observe?

Watch for these patterns in Claude's behavior:

  • Did Claude run pytest after making its fix to verify independently?
  • If it did run tests, was it grading its own homework (deciding both the fix AND whether it worked)?
  • Did anything prevent Claude from claiming success without actually verifying?

There was no independent check. Claude decided what to fix AND decided whether it was fixed. This is the limitation the maker/checker pattern solves.

Run the test suite and observe the gap

Now you will verify Claude's work yourself. This is the designed obstacle: you are about to experience why single-prompt engineering without independent verification is unreliable.

  • Before you run the tests, pause and predict: do you think Claude's fix actually works? Did anything in its process guarantee correctness?
  • Run the test suite yourself to check by running:
pytest tests/ -x -q

Observe the results. You may see one of three outcomes:

  • The original test still fails (Claude's fix was wrong).
  • A different test breaks (Claude fixed one thing but broke another).
  • All tests pass, but you had to be the verifier because nothing in Claude's process guaranteed this independently.

Why does this matter?

Regardless of whether the tests pass or fail, the core limitation is the same: there was no independent verification built into the process. You had to manually run pytest to find out whether the fix worked.

In a larger codebase with hundreds of tests, a fix that passes one test but silently breaks three others is a common failure mode. Without a separate verifier running automatically after every edit, you cannot trust the result.

  • Update the State section of CLAUDE.md with what just happened. Open the file and replace (No attempts recorded yet.) with a log entry describing:
  • Save CLAUDE.md.

Keep the state log honest

The State section is your engineering journal. Recording failures and limitations is just as valuable as recording successes. Future sessions will use this history to avoid repeating the same approaches.

🙋‍♀️ Claude Code seems stuck or keeps asking questions?

  • If Claude asks for permission to use tools, type y or allow.
  • If Claude asks clarifying questions instead of acting, exit the session with /clear and re-enter your prompt with the addition: "Do not ask me any questions. Just do it."
  • If Claude seems to loop indefinitely, press Ctrl+C to interrupt.

help me get Claude Code to complete the naive fix attempt without getting stuck

You have now experienced the core limitation firsthand: a single prompt with no independent verification gives you no confidence in the result. Next up, you will build a triage skill that gives Claude structured instructions for discovering and scoping work.

Create a Triage Skill for Work Discovery

In the last step, you asked Claude to fix a test with a single unstructured prompt. The result was unpredictable. Claude either skipped verification entirely, introduced a fix that broke other tests, or produced output that gave you no structured way to track what happened.

The problem is not Claude's reasoning ability. A skill solves this by encoding your process (find work, scope it, verify it) into a reusable file that Claude follows every time you invoke it. Instead of re-explaining the process, you write it once and let the skill handle the rest.

In this step, get ready to:
  • Create a triage skill that instructs Claude how to discover and scope failing tests.
  • Add frontmatter that pre-approves the tools the skill needs to run autonomously.
  • Verify the skill loads and produces structured triage output.
Create the skill directory and write SKILL.md

Skills live in the .claude/skills/ directory at your project root. Each skill gets its own folder containing a SKILL.md file. The directory name becomes the slash command you type to invoke it.

  • Create the skill directory by running this command:
mkdir -p .claude/skills/triage-tests
  • Confirm the directory exists by running:
ls .claude/skills/

You should see triage-tests listed in the output.

  • Create the skill file by running:
touch .claude/skills/triage-tests/SKILL.md
  • Open SKILL.md in your editor and paste the following content. This is the frontmatter and the first section of the skill:
---
name: triage-tests
description: Discover failing tests, diagnose root causes, and propose bounded fixes
allowed-tools: Bash, Edit, Write
disallowed-tools: AskUserQuestion
---

# Triage Tests Skill

You are a test-suite triage agent for this Flask fork.

## Step 1: Discover failures

Run the test suite against the targeted test file:

```
pytest tests/test_cli.py -x -q
```

If all tests pass, check the broader suite:

```
pytest tests/ -x -q --timeout=60
```

If everything passes, update CLAUDE.md State with "No failures found" and stop.

What does this frontmatter do?

The YAML block between the --- markers configures how Claude handles this skill. allowed-tools: Bash, Edit, Write pre-approves these tools so Claude can run commands and edit files without asking you for permission each time.

The disallowed-tools: AskUserQuestion line prevents Claude from pausing to ask you questions mid-run. This is critical for autonomous execution. Without it, Claude might stop halfway through a /goal session waiting for input that never comes.

  • Continue adding the following content directly below what you just pasted. This is the analysis and persistence logic:
## Step 2: Identify the failing test

From the pytest output, extract:
- The full test name (e.g., `tests/test_cli.py::test_something`)
- The assertion or exception in the traceback
- The relevant source file and line number

## Step 3: Diagnose the root cause

Read the failing test code and the source it exercises. Determine:
- Is this a logic error, a missing import, a type mismatch, or a regression?
- What is the minimal change that fixes the failure without breaking other tests?

## Step 4: Propose a bounded fix

Describe a fix that:
- Touches the fewest lines possible
- Follows Flask's existing code style
- Does NOT introduce new dependencies

## Step 5: Update CLAUDE.md State

Append to the State section of CLAUDE.md:
- Failing test name
- Diagnosis (one sentence)
- Proposed fix scope (which file, which function, what change)
- Status: "proposed"

What do steps 2 through 5 accomplish?

These steps give Claude a deterministic five-step workflow: discover, identify, diagnose, propose, and persist. Every invocation follows this same path, producing structured output you can track across sessions.

The final step (updating CLAUDE.md State) is what makes the skill "remember" its findings. Without it, each run would be stateless, and you would lose the history of what was tried.

  • Save SKILL.md.

File not saving?

Make sure you are editing the file at .claude/skills/triage-tests/SKILL.md (note the leading dot in .claude). On macOS, dot-prefixed folders are hidden by default. Use ls -la to confirm the directory exists.

Help me debug my skill file path

Verify the skill loads with /doctor

The /doctor command runs an automated check of your Claude Code installation, settings, extensions, and context usage. It confirms that your custom skill is discoverable and parseable.

  • Return to Claude Code in your Flask project directory.
  • Type /doctor and press Enter.

Before you check the output, what do you expect to see? If the skill file is in the right location with valid frontmatter, /doctor should detect it without errors.

You should see a diagnostic summary with no errors related to skills or settings. This confirms Claude Code can find and parse your SKILL.md file.

Doctor reports a skill error?

Check that the YAML frontmatter uses exactly three dashes (---) as the opening and closing markers. A missing closing --- causes a parse failure.

Also confirm the file is named exactly SKILL.md (all caps for SKILL) inside the .claude/skills/triage-tests/ directory.

Help me fix my SKILL.md frontmatter

Invoke the skill and observe structured output

Now you will run the skill and compare its output to the unstructured naive attempt from Step 2. Instead of a freeform response, Claude follows the five-step process and produces structured findings.

  • In Claude Code, type /triage-tests and press Enter.

What do you think Claude will do differently this time compared to the naive prompt? Think about what the five steps in your skill prescribe before watching the output.

Watch Claude work through each step. It runs pytest, identifies any failing test, reads the source, proposes a bounded fix, and updates the State section of CLAUDE.md. The output is structured: you see a failing test name, a one-sentence diagnosis, and a proposed fix scope.

What if all tests pass?

Flask's test suite is stable, so it is likely all tests pass. If so, Claude follows the skill's instruction and updates CLAUDE.md State with "No failures found." This proves the skill works correctly.

If you want to test the full triage path, you can introduce a deliberate failure (e.g., rename a function in Flask's CLI module), then re-invoke /triage-tests to see the complete diagnosis output.

  • After the skill finishes, open CLAUDE.md and scroll to the State section. You should see a new entry that Claude added with either a diagnosis or a "No failures found" note.

Skill not appearing when you type /triage-tests?

Skills added during a session may not appear in the slash menu until the next session starts. Exit Claude Code and reopen it in the Flask directory, then try /triage-tests again.

Help me debug why my skill isn't loading

✔️ Awesome, I've got everything!

Great. Make sure you have saved SKILL.md and confirmed the skill produces structured output (or at minimum, that /doctor found no skill errors).

ⓧ I'd like to double check the full code

Your SKILL.md should contain two sections you pasted above. Scroll up and confirm these are present in order:

  • The frontmatter block (--- markers, name, description, allowed-tools, disallowed-tools) followed by the "Triage Tests Skill" heading and Step 1 (Discover failures).
  • Steps 2 through 5 (Identify the failing test, Diagnose the root cause, Propose a bounded fix, Update CLAUDE.md State).

The full file is the two code blocks above joined together. If anything is missing, copy the relevant chunk from above and paste it into the correct position in your file.

Your triage skill encodes a repeatable process that Claude follows every time. It discovers work, scopes it, and persists findings. But there is still a gap: nothing stops Claude from skipping verification or "grading its own homework." Next up, you will add an independent verifier that runs automatically after every edit.

Add a PostToolUse Hook as the Independent Verifier

Your triage skill can now find failing tests and propose fixes. But there is a critical gap: nothing stops Claude from "grading its own homework."

The maker/checker pattern requires a separate verifier that runs independently of the agent doing the work. A PostToolUse hook solves this by running pytest automatically after every file edit. Verification happens deterministically, without Claude choosing whether to check.

In this step, get ready to:
  • Create a PostToolUse hook that fires pytest after every Python file edit.
  • Test the hook by making a trivial edit and observing automatic verification.
  • Refine the hook for fast feedback and update your project spine.
Create the PostToolUse hook configuration

Claude Code hooks are user-defined shell commands that run at specific lifecycle points. The PostToolUse event fires after a tool call executes. You can match on tool names like Edit|Write so the hook only triggers when Claude edits or writes files.

  • Create the settings file at .claude/settings.json by running this command:
touch .claude/settings.json
  • Open .claude/settings.json in your editor and paste the following hook configuration:
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "pytest tests/test_cli.py -x -q 2>&1 | tail -20"
          }
        ]
      }
    ]
  }
}

What does this configuration do?

The hooks object maps lifecycle events to arrays of matcher groups. Each group has:

  • A matcher that filters which tool triggers the hook. Edit|Write means the hook fires only when Claude edits or writes a file.
  • A hooks array of handlers. Ours uses "type": "command" to execute a shell command.
  • The command runs pytest tests/test_cli.py -x -q (stop at first failure, quiet output) and pipes the last 20 lines so feedback stays concise.
  • Save .claude/settings.json.
  • Confirm the file exists by running:
cat .claude/settings.json

You should see the full JSON object with the PostToolUse hook printed to your terminal.

Seeing a JSON parse error?

  • JSON does not allow trailing commas or comments. Remove any extra commas after the last item in an array or object.
  • Make sure the file is saved at .claude/settings.json (inside the .claude directory at your project root), not at ~/.claude/settings.json (which is the user-level settings).

Help me debug my .claude/settings.json hook configuration

Test the hook with a trivial edit

The hook is configured, but you need to prove it fires independently of Claude's judgment. You will make a trivial edit to a Flask source file and watch pytest run automatically in the terminal output.

  • Open Claude Code in your Flask repo (if not already open) by running:
claude
  • Type /hooks to open the hooks browser.
  • Select PostToolUse from the list. You should see a count of 1 next to it, and drilling in shows your Edit|Write matcher with the pytest command.
  • Press Esc to return to the CLI.

Now trigger the hook by asking Claude to make a trivial edit.

  • Type this prompt into Claude Code:
Add a comment '# hook test' at the top of src/flask/cli.py, then remove it

Before you press Enter, predict what you expect to happen. Will Claude just edit the file silently, or will something else appear in the output?

  • Press Enter and watch the terminal output.

You should see pytest results appear in the terminal after each file edit. Claude did not choose to run tests. The hook fired automatically. This is the independent verifier in action.

Why does this matter?

Without this hook, Claude decides whether to verify its own work. It might skip verification, or run tests selectively to confirm what it expects. The PostToolUse hook removes that choice. Every file edit triggers verification, regardless of whether Claude thinks it needs checking.

🙋‍♀️ Hook did not fire?

  • Run /hooks and confirm the hook appears under PostToolUse. If it does not appear, Claude Code may not have detected the file yet. Try restarting your Claude Code session.
  • Check that the matcher pattern is exactly Edit|Write. Matchers are case-sensitive.
  • Verify you are triggering the right event type. PostToolUse fires after tool execution. If Claude only read the file without editing, the hook will not fire.

Why isn't my PostToolUse hook firing when Claude edits a file?

Refine the hook and update your project spine

The hook works, but running the full test_cli.py file every time might be slow if the test suite grows. The -x flag (stop at first failure) already helps. For now, this targeted subset keeps feedback fast while still catching regressions.

The final piece is persisting this decision in your project spine so future sessions know the hook is active.

  • Open CLAUDE.md in your editor.
  • Find the ## State section.
  • Add a new line recording that the PostToolUse hook is now active:
- PostToolUse hook active: runs `pytest tests/test_cli.py -x -q` after every Python file edit via .claude/settings.json

Why record this in the spine?

The State section of CLAUDE.md persists decisions across sessions. If you (or Claude) start a new session tomorrow, the spine immediately communicates that an independent verifier is running. No re-discovery needed.

  • Save CLAUDE.md.

Your maker/checker pattern is now in place. The triage skill (maker) discovers and proposes fixes. The PostToolUse hook (checker) verifies every edit independently.

✔️ Awesome, I've got everything!

Great. Double check you have saved both .claude/settings.json and CLAUDE.md.

ⓧ I'd like to double check the full code

Here is the complete .claude/settings.json file:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "pytest tests/test_cli.py -x -q 2>&1 | tail -20"
          }
        ]
      }
    ]
  }
}

And your CLAUDE.md State section should include a line noting the PostToolUse hook is active, alongside your earlier entries about the naive fix attempt.

You now have a verifier that fires on every edit without Claude choosing whether to check. Next up, you will connect all the pieces into an autonomous loop with /goal.

Run the Full Loop with /goal

Your triage skill finds work. Your PostToolUse hook verifies every edit. Your CLAUDE.md spine remembers decisions across sessions. The pieces are ready, but right now they only fire when you prompt each step manually.

The /goal command is the final primitive that turns these pieces into an autonomous loop. It sets a completion condition, and Claude keeps working across turns until a separate evaluator model confirms the condition is met. You stop being the prompter. Claude iterates on its own.

In this step, get ready to:
  • Set a /goal condition that defines when the loop should stop.
  • Watch Claude autonomously iterate through discover, fix, and verify cycles.
  • Inspect the committed fix and updated state to confirm the full loop worked.
Set the /goal condition

The /goal command sets a completion condition for your session. After each turn Claude takes, a small fast model (Haiku by default) evaluates whether the condition is met. If not, Claude starts another turn automatically.

How /goal evaluation works

The evaluator is a separate model from the one doing the work. It reads the conversation transcript after each turn and judges whether your condition holds. This is the maker/checker pattern at the session level.

Claude does the work (maker). Haiku checks the result (checker). Neither is grading its own homework.

Before you run /goal, you need auto mode enabled so Claude can use tools without asking permission on every call. This lets goal turns run unattended.

  • Switch back to your Claude Code session in the Flask repo.
  • Press Shift+Tab to toggle auto mode on. You should see an indicator confirming auto mode is active.
  • Set your goal condition by typing the following command in Claude Code:
/goal all tests in tests/test_cli.py pass and the fix is committed with a descriptive message, or stop after 20 turns

What does this command do?

/goal followed by your condition starts a turn immediately. Claude begins working toward that condition without you sending another prompt.

The or stop after 20 turns clause bounds execution so the loop does not run indefinitely if something is genuinely unfixable.

Once you press Enter, Claude starts working. A ◎ /goal active indicator appears showing how long the goal has been running.

Watch Claude work through the loop

This is the moment where everything connects. Sit back and watch the terminal. You are no longer the prompter.

  • Watch Claude's output in the terminal as it iterates through the loop.

You should see Claude cycle through these actions across multiple turns:

  • Claude invokes the triage skill to identify failing tests and diagnose the root cause.
  • Claude writes a fix to a Python source file.
  • The PostToolUse hook fires automatically, running pytest against tests/test_cli.py in the terminal output.
  • If tests still fail, Haiku evaluates the goal condition and returns "not met." Claude takes another turn.
  • Once tests pass, Claude commits the fix with a descriptive message. Haiku evaluates again and confirms the goal is met.

What if Claude is stuck in a loop?

The or stop after 20 turns clause you added ensures Claude will stop if it cannot meet the condition. You can also press Esc to interrupt a turn, or run /goal clear to remove the goal entirely.

When the goal is achieved, Claude prints a completion message and the ◎ /goal active indicator disappears.

Goal not progressing?

If Claude asks for permission before tool calls, auto mode is not active. Press Shift+Tab to toggle it on, then set the goal again.

If the hook is not firing after edits, confirm .claude/settings.json is in the project root and the tool matcher includes Edit and Write.

Help me debug why my /goal session is not making progress toward passing tests.

Inspect the results

The goal has been achieved. Claude iterated autonomously, the hook verified each edit, and a fix is now committed. Time to confirm everything landed correctly.

Before you check the git log, what do you expect to see there? Think about what Claude was asked to do as its final action.

  • Inspect the most recent commit by running:
git log --oneline -3

You should see a new commit at the top of the log with a descriptive message explaining the fix Claude made. This proves the full loop completed: discover, execute, verify, persist.

  • Confirm the tests pass independently by running:
pytest tests/test_cli.py -x -q

All tests should pass. This is your independent verification outside of Claude's session.

  • Open CLAUDE.md and scroll to the State section to confirm Claude updated it during the loop.

The State section should now contain entries from Claude's loop session recording what was tried, what failed, and what ultimately passed. This is the "persist" step of loop engineering. The next time you or Claude opens this project, the spine remembers what happened.

What just happened?

You ran a complete loop engineering cycle using four primitives:

  • CLAUDE.md (spine) persisted state across turns.
  • The triage skill (work discovery) told Claude how to find and scope tasks.
  • The PostToolUse hook (independent verifier) ran pytest after every edit without Claude choosing to check.
  • /goal (autonomous execution) kept Claude iterating until the condition was met.

🙋‍♀️ Tests still failing after /goal completed?

If pytest shows failures when you run it manually, Claude may have committed before all tests were green. Run /goal again with the same condition to let Claude take another pass.

Help me figure out why tests are failing after my /goal session said it was complete.

Secret mission

Add a /loop Monitor for Continuous Triage

A true loop engineering system does not stop after one fix. It repeats on a cadence, finding new work each cycle. In this extension, you will wire up the /loop command to run your triage skill on a 5-minute interval, handle the case where no work is found, and watch Claude monitor the test suite continuously.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally and uses a free GitHub fork, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Forked Flask repository on GitHub (your-username/flask).
  • Local repository clone (includes .venv, .claude/ directory, and CLAUDE.md).

Keep everything running

No action needed. Choose this if you plan to apply loop engineering to other repositories or want to keep experimenting with your skill and hook setup.

  • Your fork costs nothing to keep on GitHub.
  • Your skill, hook, and CLAUDE.md spine are reusable on any future project.
  • Push your loop-engineering branch to your fork for future reference.

Pause - I'll come back to this later

Shut down any running processes to free up memory. Your files and fork stay intact for when you return.

  • The fork costs nothing to keep on GitHub.
  • If you have a Claude Code session open, you can resume anytime with claude --continue.
  • Deactivate the virtual environment by running deactivate in your terminal.

Delete - I don't want to use this again

Remove all project resources completely. This cannot be undone.

  • Delete the forked repository on GitHub:
  • Open your fork on GitHub and click Settings.
  • On the General page, scroll to the Danger Zone section and click Delete this repository.
  • Click I want to delete this repository, then click I have read and understand these effects.
  • Type your repository name to confirm, then click Delete this repository.

Delete the local repository clone by running this command:

rm -rf flask

What does this remove?

Deleting the flask folder removes everything inside it: the .venv virtual environment, the .claude/ directory (containing your skill and settings), CLAUDE.md, and all Flask source files.

Nice Work!

Nice Work!

You just built a complete loop engineering system on a real open-source codebase. Claude Code discovered work, fixed it, verified the fix independently, and committed the result. All without you prompting each step.

You've learned how to:

  • Build a complete loop engineering system using Claude Code's native primitives (skill, PostToolUse hook, /goal, and a CLAUDE.md spine) that autonomously discovers, fixes, and verifies bugs.
  • Implement the maker/checker pattern with a PostToolUse hook as the independent verifier, ensuring Claude never grades its own homework.
  • Run an autonomous /goal session where Claude iterated across multiple turns with independent verification until a stopping condition was met and the fix was committed.
  • Secret Mission: Added a /loop monitor that continuously triages the test suite on a schedule, handling both "work found" and "no work found" states.

Ready to quiz yourself?