Build and extend a real task tracker while learning Claude Code by doing real work on a real codebase.
This repo is a hands-on project for learning Claude Code features inside one working app: CLAUDE.md, custom skills, hooks, slash commands, and Plan Mode.
New to this? Start with First-Time Setup, then do Challenge 0. Already comfortable with Git / terminals? Skip to Challenge 0 or go straight to README-TUTORIAL.md.
If you're not sure what you're looking at - this is a repository, basically a project folder hosted online. Here's how to get around:
- Folders and files are listed at the top of this page. Click any folder to see what's inside, then click a file to read it.
- Go back using the breadcrumb trail near the top (e.g.,
claude-code-lab / backend / main.py) - click any part to jump back. - This page (the README) loads automatically below the file list. Most folders have one.
Go through these in order. Each link takes you directly to the file.
- You're here — this README. Setup instructions and your first challenge are below.
- PRODUCT.md — what this project is and what it teaches.
- CLAUDE.md — how Claude remembers this project between sessions.
- README-TUTORIAL.md — challenges 1–5, the hands-on work.
You need four things installed. Open PowerShell (search "PowerShell" in the Start menu) and run these commands.
winget install Python.Python.3.11Close and reopen PowerShell, then verify:
python --versionYou should see Python 3.11.x or higher.
winget install Git.GitClose and reopen PowerShell, then verify:
git --versionYou should see git version 2.x.x.
Configure your identity (use your real name and the email tied to your GitHub account):
git config --global user.name "Your Name"
git config --global user.email "you@example.com"npm install -g @anthropic-ai/claude-codeIf
npmis not recognized, install Node.js first:winget install OpenJS.NodeJS.LTS, then close and reopen PowerShell.
Verify:
claude --versionVS Code works well and has a Claude Code extension:
winget install Microsoft.VisualStudio.CodeBefore you can push code to GitHub, you need an account and a way to prove you own it from the terminal.
Go to github.com/signup and create a free account. Remember your username and email — you'll need them.
The GitHub CLI (gh) handles authentication so you never have to deal with tokens or SSH keys manually.
winget install GitHub.cliClose and reopen PowerShell, then verify:
gh --versiongh auth loginThis starts an interactive login. Here's what to pick at each prompt:
- Where do you use GitHub? →
GitHub.com - Preferred protocol? →
HTTPS - Authenticate Git with GitHub credentials? →
Yes - How would you like to authenticate? →
Login with a web browser
It will show you a one-time code and open your browser. Paste the code, authorize the app, and you're done. From now on, git push and git pull will work without asking for a password.
What just happened: authentication means proving to GitHub that you're you. The gh tool stored a secure token on your machine so git can talk to GitHub on your behalf. You only need to do this once per computer.
These steps get the app running on your machine. Run each command in PowerShell.
git clone https://github.com/james-bonaguro/claude-code-lab.gitThis downloads the entire project to a folder called claude-code-lab.
cd claude-code-labThis moves you into the project folder. All future commands assume you're here.
cd backend
pip install -r requirements.txtThis installs FastAPI, Uvicorn (the web server), pytest (for tests), and a few other packages. You should see a bunch of "Successfully installed" lines.
uvicorn main:app --reload --port 8000You should see:
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process
Leave this running. Open a new PowerShell window for the next steps.
In your new PowerShell window:
curl http://localhost:8000/healthYou should see: {"status":"healthy","task_count":0}
cd claude-code-lab
start frontend/index.htmlThis opens the task tracker UI in your browser. You should see a dark-themed page with a green "API Connected" dot in the header. Try creating a task to make sure everything works.
Open http://localhost:8000/docs in your browser. FastAPI auto-generates interactive documentation for every endpoint. You can test API calls directly from this page.
If you've never used a terminal before, this section is for you.
A terminal (also called "command line," "shell," or on Windows, "PowerShell") is an application where you type text commands instead of clicking buttons. Everything you can do by clicking around in File Explorer — open folders, move files, run programs — you can also do by typing commands. The terminal is faster once you know the basics, and it's the only way to use tools like git and Claude Code.
On Windows, search "PowerShell" in the Start menu and open it. You'll see a window with a blinking cursor. That cursor is waiting for you to type a command and press Enter.
The terminal is always "inside" a specific folder on your computer, just like File Explorer is always showing you a specific folder. This is called your working directory.
pwd— prints where you are right now (your current folder path)ls— lists all files and folders inside your current foldercd foldername— moves you into a subfoldercd ..— moves you up one level (back to the parent folder)
The most common beginner mistake is running a command from the wrong folder. If you see errors like "file not found" or "no such file or directory," run pwd to check where you are, then cd to the right place.
Example: if you're in C:\Users\James and need to be in the project:
pwd
# Output: C:\Users\James
cd claude-code-lab
pwd
# Output: C:\Users\James\claude-code-labYou'll often need two PowerShell windows open at the same time:
- Window 1: Running the API server (this window is "busy" — it shows live server logs and you can't type other commands)
- Window 2: Running git commands, Claude Code, or anything else
To open a second window: right-click PowerShell in the taskbar and click "PowerShell" again, or press Win + X and select "Terminal."
When you run a command, the terminal prints its response. Some examples:
- Green text or "Successfully installed" → things worked
- Red text or "Error" → something went wrong. Read the message — it usually tells you exactly what's broken
- Nothing at all → most commands print nothing when they succeed (like
cdorgit add). No news is good news. - A wall of scrolling text → normal for installs. Look at the last few lines to see if it succeeded or failed.
Open a new PowerShell window and navigate to the project:
cd claude-code-lab
claudeThat's it. Claude Code starts and automatically reads CLAUDE.md at the root of the project.
CLAUDE.md is Claude's persistent memory for this project. Every time you start a session, Claude reads it and immediately knows:
- The tech stack (FastAPI, SQLite, vanilla JS frontend)
- How to run the app, tests, and linter
- The project structure and where every file lives
- Common gotchas (CORS settings, hardcoded port, etc.)
This means you can ask "how do I run the tests?" and Claude answers correctly without you explaining anything. You can edit CLAUDE.md to add your own notes — Claude will remember them next session.
This repo has two custom skills that activate automatically based on what you say:
| What you say | Skill that activates | What it does |
|---|---|---|
| "Review my code" or "check code quality" | code-review |
Runs a structured security + correctness + style audit |
| "Write tests" or "add test coverage" | test-runner |
Writes pytest tests using the Arrange-Act-Assert pattern |
You don't type a command — just describe what you want in plain English and the right skill loads automatically.
Skills live in .claude/skills/*/SKILL.md. You'll create your own in Challenge 2.
Type /deploy in Claude Code to run the deployment checklist. This is a custom command defined in .claude/commands/deploy.md.
Slash commands are different from skills: you invoke them explicitly with /name, while skills activate automatically from natural language.
This repo has a PreToolUse hook configured in .claude/settings.json. Every time Claude tries to write a Python file, the hook automatically runs a syntax check and security scan. If the code has errors, Claude is blocked from writing it.
You don't need to do anything — this runs in the background. You'll see [lint] messages in the terminal when it fires.
For any change that feels non-trivial, use Plan Mode:
- Press Shift+Tab to enter Plan Mode
- Describe what you want
- Claude proposes a plan — which files change, what the approach is, what could go wrong
- Review it, ask questions, refine
- Press Shift+Tab again to approve — Claude executes the plan
Use this whenever you're unsure how deep a change goes.
Claude Code responds to plain English, but how you phrase things matters. Here are the principles:
Be specific — name the file and describe the behavior.
| Bad | Good |
|---|---|
| "Add search" | "Add a GET /tasks/search endpoint in backend/main.py that accepts a ?q= query parameter and returns tasks where the title contains that text, case-insensitive" |
| "Fix the bug" | "The frontend shows a red dot even when the server is running. Check frontend/index.html for what might be wrong with the health check" |
| "Make it better" | "Add input validation to the POST /tasks endpoint so it returns a 400 error if the title is empty" |
Reference what already exists. Instead of describing everything from scratch, point Claude at a pattern: "Add a DELETE /tasks/{id} endpoint following the same pattern as the existing PUT /tasks/{id} endpoint in main.py."
One clear ask per message. Don't pack five requests into one message. Ask for one thing, verify it works, then ask for the next thing. This gives you control over each step.
Iterate, don't front-load. Start with the simplest version, make sure it works, then build on it. "Add a search endpoint" → verify it works → "Now add pagination to the search endpoint" → verify → "Now add sorting."
When something breaks, paste the error. Don't describe the error in your own words. Copy the exact error text and paste it: "I'm getting this error when I run the tests: [paste error]. Fix it." Claude is very good at reading error messages.
You don't need to write code. Describe what you want the app to do, not the code you want written. Claude will figure out the implementation. You're the product manager, Claude is the developer.
Claude Code will sometimes show a prompt asking you to approve an action before it runs. This is a safety feature — it's making sure you're okay with what it's about to do.
Always approve these (safe actions):
- Reading files (Claude needs to read your code to help you)
- Writing/editing
.py,.html,.md,.jsonfiles - Running
python,pytest,pip install,uvicorn,curl
Pause and read before approving:
- Any shell command you don't recognize
- Anything that deletes files or uses
rm - Commands with
sudo(admin access) - Commands that interact with the internet beyond localhost
How this project reduces permission prompts: The .claude/settings.json file includes an "allow" list that pre-approves common safe actions (reading all files, writing code files, running Python and tests). You won't be prompted for those. It also has a "deny" list that blocks dangerous commands like sudo and rm -rf entirely.
If you're getting too many permission prompts for things you trust, you can ask Claude to add them to the allow list in .claude/settings.json.
These aren't obvious from the docs, but they'll save you real time:
Use @ to reference files in your prompts. Instead of typing out file paths, type @ and start typing the filename — Claude Code will autocomplete it. Example: "Add a search endpoint to @main.py that follows the pattern in @test_main.py." This is faster and less error-prone than typing full paths.
claude -c continues your last session. If you close the terminal or exit Claude Code, you don't lose everything. Run claude -c to pick up exactly where you left off — same conversation, same context.
Your CLAUDE.md survives compaction. When conversations get long, Claude automatically compresses older messages to free up space (this happens at ~95% context usage). But your CLAUDE.md is always re-loaded — your project instructions never get lost. This is why putting important info in CLAUDE.md matters more than saying it in chat.
/btw for side questions. If you're in the middle of building something and want to ask a quick question without derailing the conversation, use /btw what does this error mean? — it's a side channel that doesn't cost context or change the main task.
/doctor when things feel broken. If Claude Code is acting weird — slow, not finding files, permission errors — run /doctor. It diagnoses common installation and configuration problems.
/cost to check your spending. Claude Code uses tokens (the unit AI measures text in). Run /cost to see how many tokens you've used in the current session and what it's costing. Good habit to check periodically.
Work through these in order. Challenge 0 is your warmup. Challenges 1–5 are in README-TUTORIAL.md with full details.
This walks you through the core loop: make a change with Claude Code, verify it works, commit it to git, and push it to GitHub.
Goal: Change the app's header title from "Task Tracker" to "My Task Tracker" using Claude Code.
Step 1: Start Claude Code
cd claude-code-lab
claudeStep 2: Ask Claude to make the change
Type this in the Claude Code prompt:
Change the header title in frontend/index.html from "Task Tracker" to "My Task Tracker"
Claude will read the file, find the title, and edit it. You'll see the diff — what changed and where.
Step 3: Verify it worked
Refresh your browser (the page with the task tracker). The header should now say "My Task Tracker".
Step 4: See what changed
In a separate PowerShell window:
cd claude-code-lab
git statusThis shows which files have been modified. You should see frontend/index.html listed.
git diffThis shows the exact lines that changed — the old text with a - prefix and the new text with a + prefix.
Step 5: Stage the change
git add frontend/index.htmlThis tells git "I want to include this file in my next commit." Think of it as putting the file in a box you're about to seal.
Step 6: Commit
git commit -m "Change header title to My Task Tracker"This seals the box and labels it. The -m flag is your commit message — a short note explaining what you did and why. You should see output confirming 1 file changed.
Step 7: Push to GitHub
git pushThis uploads your commit to GitHub so it's backed up and visible to others. If this is your first push, git may ask you to set up authentication — follow the prompts.
You just completed the full development loop: edit → verify → stage → commit → push. Everything else builds on this.
Tip: You can also ask Claude Code to commit for you. Just say "commit my changes with the message 'Change header title'" and Claude will run the git commands.
Add a search endpoint to the API by describing it in plain English. Claude reads the existing code, writes the new endpoint, and the lint hook runs automatically.
See README-TUTORIAL.md → Challenge 1 for the full walkthrough.
Create an api-docs skill that auto-generates a markdown summary of all API endpoints. You'll learn how skills work by building one yourself.
See README-TUTORIAL.md → Challenge 2 for the full walkthrough.
Use Plan Mode to design JWT authentication before writing any code. Practice reviewing plans, asking follow-up questions, and understanding blast radius.
See README-TUTORIAL.md → Challenge 3 for the full walkthrough.
Get full test coverage using the test-runner skill. Learn how skills enforce consistent patterns (Arrange-Act-Assert, isolated test database, conftest fixtures).
See README-TUTORIAL.md → Challenge 4 for the full walkthrough.
Add a hook that auto-runs tests after every Python file write. Break something on purpose and watch the hook catch it instantly.
See README-TUTORIAL.md → Challenge 5 for the full walkthrough.
Problems you'll hit and how to fix them.
Windows doesn't know where Python is. Two fixes:
- Restart PowerShell — close it completely and reopen. New installs don't take effect in existing windows.
- If restarting doesn't help, reinstall Python (
winget install Python.Python.3.11) and make sure to check "Add Python to PATH" if the installer shows that option.
Something is already using port 8000 — probably a server you forgot to stop.
# Find and kill whatever is using port 8000
netstat -ano | findstr :8000
# Note the PID number at the end of the line
taskkill /PID <that_number> /FOr just close all PowerShell windows and start fresh.
The backend server isn't running. Go to your server PowerShell window and start it:
cd claude-code-lab/backend
uvicorn main:app --reload --port 8000Then refresh the browser.
You haven't authenticated with GitHub yet. Run:
gh auth loginFollow the prompts (see the "GitHub Account + Authentication" section above). After that, git push will work.
Read the error output — it usually tells you exactly which test failed and why. Common fixes:
- Schema errors or "no such table" → delete the database and restart: delete
backend/tasks.db, then restart the server - Import errors → run
cd backend && pip install -r requirements.txtto make sure all dependencies are installed - A test you didn't write is failing → paste the full error into Claude Code and say "this test is failing, fix it"
You're in the wrong folder. Check where you are:
pwdThen navigate to the right place:
cd C:\Users\YourName\claude-code-lab- If Claude is blocked from writing a file, the
PreToolUselint hook caught a syntax error. Claude will usually fix it and try again automatically. - If you see repeated permission prompts for the same action, ask Claude: "Add [that action] to the allow list in .claude/settings.json."
| Command | What it does |
|---|---|
claude |
Start a new session in the current folder |
claude -c |
Continue your last conversation (picks up where you left off) |
claude -r "name" |
Resume a session by name |
Ctrl+D |
Exit Claude Code (go back to normal terminal) |
Ctrl+C |
Cancel Claude's current action (doesn't exit) |
| Shortcut | What it does |
|---|---|
Shift+Tab |
Toggle Plan Mode (plan before executing) |
Shift+Tab+Tab |
Toggle Auto Accept (Claude acts without asking — use carefully) |
Esc Esc |
Rewind to the last checkpoint (undo Claude's recent actions) |
Tab |
Toggle extended thinking (Claude thinks harder before responding) |
@filename |
Reference a file in your prompt (autocompletes as you type) |
Ctrl+L |
Clear the screen (keeps conversation, just cleans up the display) |
Ctrl+R |
Search through your previous commands |
| Command | What it does |
|---|---|
/help |
Show all available commands |
/compact |
Compress conversation context (auto-triggers at ~95% full) |
/clear |
Clear the conversation and start fresh |
/cost |
Show token usage and cost for the current session |
/context |
Show context usage and optimization tips |
/doctor |
Diagnose installation and configuration problems |
/init |
Generate a CLAUDE.md for any project (use on your own repos) |
/memory |
Edit your CLAUDE.md files |
/model |
Switch between Claude models |
/skills |
List all available skills in the project |
/hooks |
View and manage active hooks |
/btw |
Ask a side question without derailing the main task |
/deploy |
Run the deployment checklist (custom to this project) |
| Command | What it does |
|---|---|
git status |
Show which files have changed |
git diff |
Show the exact line-by-line changes |
git add <file> |
Stage a file for the next commit |
git add . |
Stage all changed files |
git commit -m "message" |
Save staged changes with a description |
git push |
Upload commits to GitHub |
git pull |
Download latest changes from GitHub |
git log --oneline |
Show recent commit history (one line each) |
git branch |
Show which branch you're on |
| Command | What it does |
|---|---|
cd foldername |
Move into a folder |
cd .. |
Move up one folder |
ls |
List files in the current folder |
pwd |
Print the current folder path |
start filename |
Open a file with its default app |
Ctrl+C |
Stop a running process (like the server) |
cls |
Clear the terminal screen |
| Command | What it does |
|---|---|
cd backend && pip install -r requirements.txt |
Install Python dependencies |
cd backend && uvicorn main:app --reload --port 8000 |
Start the API server |
start frontend/index.html |
Open the frontend in your browser |
cd backend && python -m pytest tests/ -v |
Run all tests with details |
bash scripts/lint.sh |
Run the linter manually |
curl http://localhost:8000/health |
Check if the API is running |
Every technical term you'll encounter in this project, explained in plain English.
| Term | What it means |
|---|---|
| API | A way for programs to talk to each other. The backend is an API — the frontend sends it requests ("give me all tasks") and gets back responses (a list of tasks in JSON). |
| Auto Accept | A Claude Code mode (toggle with Shift+Tab+Tab) where Claude executes actions without asking for your permission first. Faster, but use carefully — you're giving Claude a longer leash. |
| Backend | The part of the app that runs on a server, stores data, and responds to requests. In this project, it's Python code using FastAPI. You don't see it in the browser — it works behind the scenes. |
| Branch | A parallel version of your code. Like making a copy of a document to try edits without changing the original. The main branch is main. You can create other branches to experiment safely. |
| CLI | Command Line Interface. A program you use by typing commands instead of clicking buttons. Claude Code and git are both CLIs. |
| Clone | Downloading a copy of a GitHub repository to your computer. git clone creates a local folder with all the project's files and history. |
| Compaction | When Claude compresses older parts of the conversation to free up space. Happens automatically at ~95% context usage. Your CLAUDE.md is always re-loaded after compaction, so project instructions are never lost. |
| Commit | A saved snapshot of your changes, with a message describing what you did. Think of it like a save point in a video game — you can always go back to any commit. |
| Context window | The amount of conversation Claude can "see" at once. Think of it as Claude's short-term memory — it has a limit. When it fills up, compaction kicks in to make room. |
| CORS | Cross-Origin Resource Sharing. A browser security rule that controls which websites can talk to your API. This project sets it to allow everything ("*") for development. |
| CRUD | Create, Read, Update, Delete — the four basic operations for managing data. This task tracker has endpoints for all four. |
| Dependency | A package or library your project needs to run. Listed in requirements.txt. When you run pip install -r requirements.txt, you're installing all dependencies. |
| Diff | The difference between two versions of a file. Lines added show a + prefix, lines removed show a - prefix. git diff shows you the diff of your current changes. |
| Endpoint | A specific URL your API responds to. GET /tasks is one endpoint, POST /tasks is another. Each endpoint does one thing. |
| Extended thinking | A mode where Claude thinks longer and harder before responding. Toggle it with Tab. Useful for complex problems. Uses more tokens but gives better answers. |
| FastAPI | The Python framework (a set of pre-built tools) used to build the backend. It handles routing URLs to code, validating data, and generating API docs automatically. |
| Frontend | The part of the app users see and interact with in the browser. In this project, it's a single HTML file with built-in CSS and JavaScript. |
| Git | A version control tool that tracks every change to your code. Like "Track Changes" in Word, but for an entire project. It lets you undo mistakes, see history, and collaborate. |
| GitHub | A website that hosts git repositories online. It's where your code is backed up, shared, and collaborated on. The project lives at github.com in your repository. |
| Hook | A script that runs automatically at a specific moment. This project has a PreToolUse hook that checks Python syntax every time Claude tries to write a .py file. If the syntax is wrong, the write is blocked. |
| HTTP methods | The verbs that tell an API what to do. GET = read/fetch data. POST = create new data. PUT = update existing data. DELETE = remove data. |
| JSON | JavaScript Object Notation. A data format that looks like {"title": "Buy milk", "priority": "high"}. APIs send and receive JSON. Everything between { } with "key": "value" pairs is JSON. |
| Linter | A tool that checks code for errors or style problems without actually running it. The scripts/lint.sh file in this project is a linter that catches Python syntax errors. |
| Localhost | Your own computer acting as a server. When you see http://localhost:8000, that means the app is running on your machine, port 8000. Only you can access it. |
| MCP | Model Context Protocol. A way to connect Claude to external tools and services (like GitHub, databases, or APIs). Advanced feature — you won't need this as a beginner, but you'll see it mentioned in docs. |
| npm | Node Package Manager. Used to install JavaScript tools like Claude Code. npm install -g installs a tool globally (available everywhere on your computer). |
| PATH | A list of folders your computer searches when you type a command. If python is "not recognized," it means Python's folder isn't on your PATH. Reinstalling usually fixes this. |
| Pip | Python's package installer. pip install fastapi downloads and installs FastAPI. pip install -r requirements.txt installs everything listed in that file. |
| Port | A numbered channel for network traffic on your computer. This app uses port 8000. If something else is using port 8000, you'll get an "address already in use" error. |
| PowerShell | Windows' built-in terminal application. Where you type commands to run programs, navigate folders, and use tools like git. |
| Pull / Push | Pull = download the latest changes from GitHub to your computer. Push = upload your local commits to GitHub. Think of GitHub as a shared folder — pull to get updates, push to share yours. |
| Pydantic | A Python library for data validation. It enforces rules like "task title must be between 1 and 200 characters" and "priority must be low, medium, or high." Defined in models.py. |
| Repository (repo) | A project folder tracked by git. Contains your code, its entire change history, and configuration files. claude-code-lab is a repository. |
| REST | A pattern for designing APIs. Each URL represents a resource (like /tasks), and you use HTTP methods to interact with it: GET to read, POST to create, PUT to update, DELETE to remove. |
| Skill | A Claude Code feature — a markdown file (.claude/skills/*/SKILL.md) that gives Claude specific instructions for a task like code review or test writing. Skills activate automatically based on what you say. |
| SQLite | A lightweight database stored in a single file. This project stores all tasks in backend/tasks.db. No setup needed — it creates itself when the app starts. |
| Stage (git) | Marking files to include in the next commit. git add main.py stages main.py. Think of it as putting files in a box before you seal it (commit). |
| Terminal | The application where you type commands. On Windows this is PowerShell. Same thing as "command line," "shell," or "CLI." It's just a text-based way to control your computer. |
| Token | The unit AI uses to measure text. Every word you send and every word Claude responds with costs tokens. One token is roughly 3/4 of a word. Run /cost to see your token usage. |
| Uvicorn | The web server that runs the FastAPI backend. When you run uvicorn main:app, Uvicorn starts listening for HTTP requests and passes them to your FastAPI code. |
| Working directory | The folder your terminal is currently "inside." Every command you run happens relative to this folder. pwd shows it, cd changes it. |