Claude Code in Action
Coding Assistant:
· Coding assistants use language models to complete different tasks
· Language models need tools to handle most real-world programming tasks
· Not all language models use tools with the same skill level
· Claude’s strong tool use enables better security, customization, and longevity in Claude Code
Coding Assistant Working Framework
- Gather context – Understanding what the error refers to, which part of the codebase is affected, and what files are relevant
- Formulate a plan – Deciding how to solve the issue, such as changing code and running tests to verify the fix
- Take action – Actually implementing the solution by updating files and running commands
The first and last steps require the assistant to interact with the outside world – reading files, fetching documentation, running commands, or editing code.
Tool Use:
– Language models by themselves can only process text and return text – they can’t actually read files or run commands. For handling this challenge is being used Tool
How Tool Use Works:
– When sending request to coding assistant, it automatically adds instructions to your message that teach how to request actions. Ex: Read a file, respond with ‘ReadFile: name of file'”
– Flow:
– You ask: “What code is written in the main.go file?”
– The coding assistant adds tool instructions to your request
– The language model responds: “ReadFile: main.go”
– The coding assistant reads the actual file and sends its contents back to the model
– The language model provides a final answer based on the file contents
This system allows language models to effectively “read files,” “write code,” and “run commands” even though they’re really just generating carefully formatted text responses.
Why Claude’s Tool Use Matters:
Benefits of Strong Tool Use:
- Tackles harder tasks – Claude can combine different tools to handle complex work and will use tools it hasn’t seen before
- Extensible platform – Easily add new tools to Claude Code, and Claude will adapt to use them as your workflow evolves
- Better security – Claude Code can navigate codebases without requiring indexing, which often means not sending your entire codebase to external servers
Tools with Claude Code:
Claude Code setup
– You can find full setup instructions here: https://code.claude.com/docs/en/quickstart
– Install Claude Code
o MacOS, Linux, WSL: curl -fsSL https://claude.ai/install.sh | bash
o Windows PowerShell: irm https://claude.ai/install.ps1 | iex
o Windows Command Prompt (cmd.exe): curl -fsSL https://claude.ai/install.cmd -o install.cmd && install.cmd && del install.cmd
o MacOS (Homebrew): brew install --cask claude-code
– After installation, run claude at your terminal. The first time you run this command you will be prompted to pick a color theme for the terminal and authenticate with your claude.ai credentials
If you get an error that claude isn’t found after installing, or you hit a network or permissions error, see Troubleshoot installation issues in the docs.
Project Setup
This project requires a small amount of setup:
- Ensure you have Node JS installed locally. Link to installation directions.
- Download the zip file called uigen.zip attached to this lecture and extract it
- In the project directory, run npm run setup to install dependencies and set up a local SQLite database
- Optional: this project uses Claude through the Anthropic API to generate UI components. If you want to fully test out the app, you will need to provide an API key to access the Anthropic API. You can skip this, and the app will still generate some static fake code. Here’s how you can set the API key:
- Get an Anthropic API key at https://console.anthropic.com/
- Place your API key in the .env file. Replace the literal text your-api-key-here with your key from the Anthropic console.
- Start the project by running npm run dev
Adding Context
The /init Command
When you first start Claude in a new project, run the /init command. This tells Claude to analyze your entire codebase and understand:
· The project’s purpose and architecture
· Important commands and critical files
· Coding patterns and structure
After analyzing your code, Claude creates a summary and writes it to a CLAUDE.md file. When Claude asks for permission to create this file, you can either hit Enter to approve each write operation, or press Shift+Tab to let Claude write files freely throughout your session.
The CLAUDE.md File
The CLAUDE.md file serves two main purposes:
· Guides Claude through your codebase, pointing out important commands, architecture, and coding style
· Allows you to give Claude specific or custom directions
This file gets included in every request you make to Claude, so it’s like having a persistent system prompt for your project.
CLAUDE.md File Locations
Claude recognizes three different CLAUDE.md files in three common locations:
· CLAUDE.md – Generated with /init, committed to source control, shared with other engineers
· CLAUDE.local.md – Not shared with other engineers, contains personal instructions and customizations for Claude
· ~/.claude/CLAUDE.md – Used with all projects on your machine, contains instructions that you want Claude to follow on all projects
Adding Custom Instructions
For example, if Claude is adding too many comments to code by updating the file(Edit CLAUDE.md directly in your editor, or run /memory inside Claude Code to open the file) Add an instruction like Use comments sparingly. Only comment complex code.
Claude reads this file at the start of every conversation, so changes apply to your next message.
File Mentions with ‘@’
Need Claude to look at specific files, use the @ symbol followed by the file path.
For example, if you want to ask about your authentication system and you know the relevant files, you can type:
How does the auth system work? @auth
Claude will show you a list of auth-related files to choose from, then include the selected file in your conversation.
Referencing Files in CLAUDE.md
Directly mention files in CLAUDE.md file using the same @ syntax.
For example, if you have a database schema file that defines your data structure, you might add this to your CLAUDE.md:
The database schema is defined in the @prisma/schema.prisma file. Reference it anytime you need to understand the structure of data stored in the database.
When you mention a file this way, its contents are automatically included in every request, so Claude can answer questions about your data structure immediately without having to search for and read the schema file each time.
This is also useful if your repo already has an AGENTS.md file for another tool. You don’t need to duplicate the instructions; add @AGENTS.md on the first line of your CLAUDE.md, and Claude will load the contents of that file first. Then, you can add any Claude-specific instructions below the import.
Making changes
– Using Screenshots for Precise Communication
– To paste a screenshot into Claude, use Ctrl+V (not Cmd+V on macOS)
Planning Mode
– Claude do thorough exploration of your project before implementing changes
Enable Planning Mode by typing /plan or by pressing Shift + Tab twice (or once if you’re already auto-accepting edits). In this mode, Claude will:
– Read more files in your project
– Create a detailed implementation plan
– Show you exactly what it intends to do
– Wait for your approval before proceeding
Tip: when reviewing the plan, you can press Ctrl+G to open it in your text editor. You can precise edits before approving the plan, and Claude will see the final version you submit.
Effort level: how hard Claude thinks
– By default, Claude reasons through problems before answering.
– To see Claude’s reasoning process, press Ctrl+O to expand the actual reasoning steps
– /effort To control how Claude reasons through Run /effort to see your current level and adjust it low is faster and cheaper, max reasons longest on hard problems. The default depends on your model and plan — /effort shows you what yours is.
– To signal Claude to Extra thinking in a single prompt use keyword ultrathink in your prompt
When to Use Planning vs Effort
Planning Mode is best for:
· Tasks requiring broad understanding of your codebase
· Multi-step implementations
· Changes that affect multiple files or components
Adjusting to a higher effort level is best for:
· Complex logic problems
· Debugging difficult issues
· Algorithmic challenges
Controlling context
· Interrupting Claude with Escape
· Combining Escape with Memories When Claude makes the same mistake repeatedly across different conversations, you can:
- Press Escape to stop the current response
- Run /memory (or edit CLAUDE.md directly) to add a note about the correct approach
- Continue the conversation with the corrected information
Rewinding Conversations
During long conversations, you might accumulate context that becomes irrelevant or distracting. For instance, if Claude encounters an error and spends time debugging it, that back-and-forth discussion might not be useful for the next task.
You can rewind the conversation by pressing Escape twice or typing /rewind. This shows you all the messages you’ve sent, allowing you to jump back to an earlier point and continue from there. This technique helps you:
- Maintain valuable context (like Claude’s understanding of your codebase)
- Remove distracting or irrelevant conversation history
- Keep Claude focused on the current task
Context Management Commands
/compact Summarizes your entire conversation history while preserving the key information
- Claude has gained valuable knowledge about your project
- You want to continue with related tasks
- The conversation has become long but contains important context
/clear starts a new conversation with fresh context
Custom commands
Creating Custom Commands
- need to set up a specific folder structure in your project
- Find the .claude folder in your project directory
- Create a new directory called commands inside it
- Create a new markdown file with your desired command name (like audit.md)
The filename becomes your command name – so audit.md creates the /audit command.
Example: Audit Command
This audit command does three things:
- Runs npm audit to find vulnerable installed packages
- Runs npm audit fix to apply updates
- Runs tests to verify the updates didn’t break anything
Commands with Arguments
- Custom commands can accept arguments using the $ARGUMENTS placeholder
- For example, a write_tests.md
Write comprehensive tests for: $ARGUMENTS
Testing conventions:
* Use Vitest with React Testing Library
* Place test files in a __tests__ directory in the same folder as the source file
* Name test files as [filename].test.ts(x)
* Use @/ prefix for imports
Coverage:
* Test happy paths
* Test edge cases
* Test error states
· You can then run this command with a file path:
· /write_tests the use-auth.ts file in the hooks directory
The arguments don’t have to be file paths – they can be any string you want to pass to give Claude context and direction for the task.
MCP servers with Claude Code
- Extend Claude Code’s capabilities by adding MCP (Model Context Protocol) servers. These servers run either remotely or locally on your machine and provide Claude with new tools and abilities it wouldn’t normally have.
- Popular MCP servers is Playwright, which gives Claude the ability to control a web browser. This opens up powerful possibilities for web development workflows.
Installing the Playwright MCP Server
claude mcp add playwright npx @playwright/mcp@latest
- Names the MCP server “playwright”
- Provides the command that starts the server locally on your machine
Managing Permissions
- Claude will ask for permission each time, to avoid set the pre approve to edit permission.
- Open the .claude/settings.local.json file and add the server to the allow
{
“permissions”: {
“allow”: [“mcp__playwright”],
“deny”: []
}
}
Practical Example: Improving Component Generation
Here’s a real-world example of how the Playwright MCP server can improve your development workflow. Instead of manually testing and tweaking prompts, you can have Claude:
- Open a browser and navigate to your application
- Generate a test component
- Analyze the visual styling and code quality
- Update the generation prompt based on what it observes
- Test the improved prompt with a new component
For instance, you might ask Claude to:
“Navigate to localhost:3000, generate a basic component, review the styling, and update the generation prompt at @src/lib/prompts/generation.tsx to produce better components going forward.”
Claude will use the browser tools to interact with your app, examine the generated output, and then modify your prompt file to encourage more original and creative designs.
Results and Benefits
In practice, this approach can lead to significantly better results. Instead of generic purple-to-blue gradients and standard Tailwind patterns, Claude might update prompts to encourage:
- Warm sunset gradients (orange-to-pink-to-purple)
- Ocean depth themes (teal-to-emerald-to-cyan)
- Asymmetric designs and overlapping elements
- Creative spacing and unconventional layouts
The key advantage is that Claude can see the actual visual output, not just the code, which allows it to make much more informed decisions about styling improvements.
Exploring Other MCP Servers
Playwright is just one example of what’s possible with MCP servers. The ecosystem includes servers for:
- Database interactions
- API testing and monitoring
- File system operations
- Cloud service integrations
- Development tool automation
Consider exploring MCP servers that align with your specific development needs. They can transform Claude from a code assistant into a comprehensive development partner that can interact with your entire toolchain.
Github integration
Setting Up the Integration
– run /install-github-app in Claude
– Install the Claude Code app on GitHub
– Add your API key
– Automatically generate a pull request with the workflow files
The generated pull request adds two GitHub Actions to your repository. Once merged, you’ll have the workflow files in your .github/workflows directory.
Default GitHub Actions
The integration provides two main workflows:
Mention Action
You can mention Claude in any issue or pull request using @claude. When mentioned, Claude will:
· Analyze the request and create a task plan
· Execute the task with full access to your codebase
· Respond with results directly in the issue or PR
Pull Request Action
Whenever you create a pull request, Claude automatically:
· Reviews the proposed changes
· Analyzes the impact of modifications
· Posts a detailed report on the pull request
Customizing the Workflows
After merging the initial pull request, you can customize the workflow files to fit your project’s needs. Here’s how to enhance the mention workflow:
Adding Project Setup
Before Claude runs, you can add steps to prepare your environment:
– name: Project Setup
run: |
npm run setup
npm run dev:daemon
Custom Instructions
Provide Claude with context about your project setup:
custom_instructions: |
The project is already set up with all dependencies installed.
The server is already running at localhost:3000. Logs from it
are being written to logs.txt. If needed, you can query the
db with the ‘sqlite3’ cli. If needed, use the mcp__playwright
set of tools to launch a browser and interact with the app.
MCP Server Configuration
You can configure MCP servers to give Claude additional capabilities:
mcp_config: |
{
“mcpServers”: {
“playwright”: {
“command”: “npx”,
“args”: [
“@playwright/mcp@latest”,
“–allowed-origins”,
“localhost:3000;cdn.tailwindcss.com;esm.sh”
]
}
}
}
Tool Permissions
When running Claude in GitHub Actions, you must explicitly list all allowed tools. This is especially important when using MCP servers.
allowed_tools: “Bash(npm:*),Bash(sqlite3:*),mcp__playwright__browser_snapshot,mcp__playwright__browser_click,…”
Unlike local development, there’s no shortcut for permissions in GitHub Actions. Each tool from each MCP server must be individually listed.
Best Practices
When setting up Claude’s GitHub integration:
· Start with the default workflows and customize gradually
· Use custom instructions to provide project-specific context
· Be explicit about tool permissions when using MCP servers
· Test your workflows with simple tasks before complex ones
· Consider your project’s specific needs when configuring additional steps
The GitHub integration transforms Claude from a development assistant into an automated team member that can handle tasks, review code, and provide insights directly within your GitHub workflow.
Hooks and SDK
Introducing hooks
– Hooks allow you to run commands before or after Claude attempts to run a tool.
– They’re incredibly useful for implementing automated workflows like running code formatters after file edits, executing tests when files change, or blocking access to specific files.
– Hooks insert themselves into this process, allowing you to execute code just before or just after the tool execution happens.
– PreToolUse hooks – Run before a tool is called
– PostToolUse hooks – Run after a tool is called
Hook Configuration
· Global – ~/.claude/settings.json (affects all projects)
· Project – .claude/settings.json (shared with team)
· Project (not committed) – .claude/settings.local.json (personal settings)
You can write hooks by hand in these files or use the /hooks command inside Claude Code.
Practical Applications
Here are some common ways to use hooks:
- Code formatting – Automatically format files after Claude edits them
- Testing – Run tests automatically when files are changed
- Access control – Block Claude from reading or editing specific files
- Code quality – Run linters or type checkers and provide feedback to Claude
- Logging – Track what files Claude accesses or modifies
- Validation – Check naming conventions or coding standards
Available Tools
Tool Call Data Structure
When your hook command executes, Claude sends JSON data through standard input containing details about the proposed tool call:
{
“session_id”: “2d6a1e4d-6…”,
“transcript_path”: “/Users/sg/…”,
“hook_event_name”: “PreToolUse”,
“tool_name”: “Read”,
“tool_input”: {
“file_path”: “/code/queries/.env”
}
}
Your command reads this JSON from standard input, parses it, and then decides whether to allow or block the operation based on the tool name and input parameters.
Exit Codes and Control Flow
· Exit Code 0 – Everything is fine, allow the tool call to proceed
· Exit Code 2 – Block the tool call (PreToolUse hooks only)
Implementing a hook
Configure the hook
Open .claude/settings.local.json and add a PreToolUse hook that matches the Read tool:
{
“hooks”: {
“PreToolUse”: [
{
“matcher”: “Read”,
“hooks”: [
{ “type”: “command”, “command”: “node $PWD/hooks/read_hook.js” }
]
}
]
}
}
Write the hook script
Create hooks/read_hook.js:
process.stdin.setEncoding(“utf8”);
let input = “”;
process.stdin.on(“data”, (d) => (input += d));
process.stdin.on(“end”, () => {
const toolArgs = JSON.parse(input);
const readPath = toolArgs.tool_input?.file_path || “”;
if (readPath.includes(“.env”)) {
console.error(“You cannot read the .env file”);
process.exit(2);
}
process.exit(0);
});
The hook reads the tool call from stdin as JSON, checks tool_input.file_path, and exits with code 2 to block the call (anything written to stderr becomes the message Claude sees).
Test it
In a Claude Code session, ask Claude to read your .env file. You should see:
You cannot read the .env file
That’s the hook blocking the Read call. Ask Claude to read a different file and it works normally.
Why Read only?
Each tool sends a different input shape. Read sends {“file_path”: “…”}; Grep sends {“pattern”: “…”, “path”: “…”} where path is a search directory, not a file; Bash sends {“command”: “…”}. A check on file_path catches Read but won’t catch a project-wide grep for API_KEY or a cat .env in Bash. To cover those, you’d write separate matchers per tool and inspect each one’s specific fields — or use permissions.deny rules, which apply uniformly across tools.
Gotchas around hooks
You may notice that after running the npm run setup command there are two settings.json files in the .claude directory.
– One of the recommendations is to use absolute paths (rather than relative paths) for scripts. This helps mitigate path interception and binary planting attacks.
– The absolute path to any of the hook scripts on your machine will likely be different from the absolute path on my machine
– To solve this problem, our project has a settings.example.json file. Inside of it, the script references contain a $PWD placeholder. When we run npm run setup, some dependencies are installed, but it also runs an init-claude.js script placed inside the scripts directory. This script will replace those $PWD placeholder with the absolute path to the project on your machine, copy the settings.example.json file, and rename it to settings.local.json.
– This script allows us to share settings.json files but still use the recommended absolute paths!
–
Useful hooks!
TypeScript Type Checking Hook:
when Claude modifies a function signature, it often doesn’t update all the places where that function is called throughout your project.
Example: Ask Claude to add a verbose parameter to a function in schema.ts, it will successfully update the function definition but miss the call site in main.ts. This creates type errors.
Solution:
post-tool-use hook that runs the TypeScript compiler after every file edit:
· Runs tsc –noEmit to check for type errors
· Captures any errors found
· Feeds the errors back to Claude immediately
· Prompts Claude to fix the issues in other files
Query Duplication Prevention Hook
· Claude sometimes creates duplicate functionality instead of reusing existing code
· It’s especially problematic when you give Claude complex, multi-step tasks that include database operations as just one component.
Consider a project structure with multiple query files, each containing many SQL functions. When you ask Claude to “create a Slack integration that alerts about orders pending longer than 3 days,” it might write a new query instead of using the existing getPendingOrders() function.
The query duplication hook addresses this by implementing a review process:
Here’s how it works:
- Triggers when Claude modifies files in the ./queries directory
- Launches a separate instance of Claude Code programmatically
- Asks the second instance to review the changes and check for similar existing queries
- If duplicates are found, provides feedback to the original Claude instance
- Prompts Claude to remove the duplicate and use the existing functionality
Implementation Considerations
Both hooks use the pre-tool-use or post-tool-use hook system. The TypeScript hook is relatively lightweight and runs quickly. The query duplication hook requires more resources since it launches a separate Claude instance for each review.
For the query hook, consider these trade-offs:
- Benefits: Cleaner codebase with less duplication
- Costs: Additional time and API usage for each query directory edit
- Recommendation: Only monitor critical directories to minimize overhead
The hooks use Claude’s Agent SDK to programmatically interact with the AI. This allows you to create sophisticated workflows where one Claude instance can review and provide feedback on another’s work.
Extending These Concepts
These hooks demonstrate broader principles you can apply to your own projects:
- Use compiler/linter output to provide immediate feedback
- Implement code review processes using separate AI instances
- Focus monitoring on high-value directories where consistency matters most
- Balance automation benefits against performance costs
The key is identifying the specific pain points in your development workflow and creating targeted hooks that address those issues automatically.
Useful hooks:
- Notification – Runs when Claude Code sends a notification, which occurs when Claude needs permission to use a tool, or after Claude Code has been idle for 60 seconds
- Stop – Runs when Claude Code has finished responding
- SubagentStop – Runs when a subagent (these are displayed as a “Task” in the UI) has finished
- PreCompact – Runs before a compact operation occurs, either manual or automatic
- UserPromptSubmit – Runs when the user submits a prompt, before Claude processes it
- SessionStart – Runs when starting or resuming a session
- SessionEnd – Runs when a session ends
Confusing part:
- The stdin input to your commands will change based upon the type of hook being executed (PreToolUse, PostToolUse, Notification, etc)
- The tool_input contained in that will differ based upon the tool that was called (in the case of PreToolUse and PostToolUse hooks)
The Claude Code SDK
The Agent SDK lets you run Claude Code programmatically from your own applications and scripts. It’s available for TypeScript and Python, and gives you the same agent loop the CLI uses — file reading, editing, tool use — under your control.
Install
Create a directory for the project and install the SDK package:
mkdir sdk-demo
cd sdk-demo
npm init -y
npm install @anthropic-ai/claude-agent-sdk
The package is @anthropic-ai/claude-agent-sdk. (The similarly-named @anthropic-ai/claude-code is the CLI itself and can’t be imported.)
Example:
Create a file called index.mjs using your editor (or nano index.mjs in the terminal) and paste:
import { query } from “@anthropic-ai/claude-agent-sdk”;
const prompt = “List the files in the current directory”;
for await (const message of query({ prompt })) {
console.log(JSON.stringify(message, null, 2));
}
Run it:
node index.mjs
You’ll see a stream of JSON messages — the same conversation events you’d see in the CLI, including tool calls, tool results, and Claude’s text.
Restricting tools
By default the SDK has access to the full tool set. To narrow it, pass allowedTools:
for await (const message of query({
prompt,
options: { allowedTools: [“Read”, “Glob”] },
})) {
// …
}
This is the SDK equivalent of the CLI’s –allowedTools flag.
Where to go next
The SDK supports everything the CLI does: custom system prompts, MCP servers, hooks, subagents, and session resumption. See the Agent SDK documentation for the full reference.


Leave feedback about this