Claude Code: The Complete Guide to Anthropic's CLI Coding Agent
Master Anthropic's agentic coding tool: architecture, tool use, context management, MCP integration, CI/CD pipelines, and production workflows
Table of Contents
- Introduction — The Rise of Agentic Coding
- What Is Claude Code?
- Architecture and How It Works
- Installation and Setup
- Core Features — File Editing, Bash Execution, and Search
- Multi-Turn Agentic Workflow
- Tool Use and Function Calling
- Context Window Management
- System Prompt Engineering
- Custom Instructions with CLAUDE.md
- Permissions and Security Model
- MCP — Model Context Protocol Integration
- GitHub Actions and CI/CD Integration
- Hooks and Automation
- Git Workflow Integration
- Comparing Claude Code vs Cursor vs GitHub Copilot
- Pricing and Token Usage
- Best Practices and Tips
- Common Pitfalls
- Real-World Use Cases
- Extending Claude Code with Plugins
- Performance Optimization
- Privacy and Data Handling
- The Future of AI Coding Agents
- Interview Q&A
1. Introduction — The Rise of Agentic Coding
The software development landscape has undergone a seismic shift in the past two years. What began with simple autocomplete tools that suggested the next line of code has evolved into full agentic coding systems that can understand entire codebases, execute terminal commands, modify multiple files, run tests, and iterate on their own output until a task is complete. Claude Code sits at the forefront of this revolution as Anthropic's flagship CLI-based coding agent, and understanding its architecture, capabilities, and limitations is essential for any senior engineer who wants to remain productive in the age of AI-assisted development.
The transition from "AI autocomplete" to "AI agent" is not merely a marketing evolution. It represents a fundamental change in the interaction model between developers and their tools. Traditional code assistants are reactive: you type, they suggest, you accept or reject. Claude Code is proactive: you describe a task, it formulates a plan, executes a sequence of tool calls, reads the results, and continues working until the task is done. This agentic loop — observe, plan, act, reflect — is what makes Claude Code qualitatively different from its predecessors.
Since its initial release in early 2025, Claude Code has rapidly become one of the most popular agentic coding tools in the industry. It runs directly in your terminal, has full access to your filesystem and shell, and can operate on codebases of any size through intelligent context management. Unlike IDE-bound tools, Claude Code works across any project, any language, and any workflow. It integrates with GitHub Actions for automated code review, supports the Model Context Protocol for extending its capabilities, and provides granular permission controls that make it suitable for enterprise use.
This guide is written for senior and staff-level engineers who want a deep, technical understanding of Claude Code. We will go beyond surface-level tutorials and explore the internal architecture, the agentic loop, context window management, system prompt engineering, the permission model, MCP integration, CI/CD pipelines, hooks, and the broader ecosystem. We will include C# code examples for building custom integrations, HTML tables comparing features and pricing, Mermaid diagrams illustrating architecture, and an interview Q&A section that covers the questions you are most likely to encounter when discussing AI coding agents in a technical interview setting.
2. What Is Claude Code?
Claude Code is an agentic coding tool built by Anthropic that runs in your terminal and gives you a coding agent powered by the Claude family of language models. It is not an IDE plugin or an autocomplete system. It is a standalone CLI application that can read your files, write new code, execute shell commands, run tests, search through codebases, manage git branches, create pull requests, and interact with external services through MCP servers. You interact with it through natural language, and it uses a set of built-in tools to accomplish tasks across your local development environment.
At its core, Claude Code is built on the Claude 3.5 Sonnet and Claude Opus model families. When you send a message, Claude Code constructs a system prompt that includes your project context, any CLAUDE.md instructions, conversation history, and the available tool definitions. The model then generates a response that may include text, tool calls, or both. The Claude Code runtime executes those tool calls, feeds the results back to the model, and the loop continues until the task is complete or the model determines it has enough information to respond.
Key Characteristics
- Terminal-native: Runs as a CLI tool in your terminal, not tied to any specific IDE or editor
- Agentic: Can autonomously plan and execute multi-step tasks with tool use
- Full environment access: Can read files, write files, execute shell commands, and interact with git
- Context-aware: Manages context windows intelligently, supporting codebases far larger than a single context window
- Extensible: Supports MCP servers for custom tool integrations and CLAUDE.md for project-specific instructions
- Permission-controlled: Granular permission system that controls which tools can be used automatically vs. requiring user approval
- Multi-model: Can use different Claude models depending on the task complexity and cost requirements
Claude Code vs Traditional Code Assistants
| Feature | Traditional Assistants | Claude Code |
|---|---|---|
| Interaction Model | Autocomplete / chat | Agentic task execution |
| File Access | Open editor tab only | Full filesystem |
| Shell Access | None or limited | Full shell execution |
| Multi-File Edits | Manual, one file at a time | Automatic across codebase |
| Test Execution | Not possible | Runs tests, reads results, iterates |
| Git Integration | Basic (commit messages) | Branching, commits, PRs, code review |
| Extensibility | Plugin APIs | MCP servers + hooks + CLAUDE.md |
| Context Window | Single file or small selection | Full codebase with smart compression |
| Running Environment | Inside IDE | Terminal (any project, any language) |
The fundamental difference is autonomy. A traditional code assistant waits for you to tell it what to do on a line-by-line basis. Claude Code receives a high-level goal and autonomously determines which files to read, which commands to run, what code to write, and how to verify its work. This is the difference between a spell-checker and a junior developer who can take a ticket, explore the codebase, implement a solution, write tests, and open a pull request — except this junior developer operates at the speed of an API call.
Claude Code also introduces the concept of "extended thinking" where the model can reason through complex problems step by step before generating a solution. This is particularly valuable for architectural decisions, debugging complex issues, and navigating large codebases where the model needs to consider multiple approaches before committing to one. The extended thinking capability is visible in the terminal as a separate output stream that shows the model's reasoning process.
3. Architecture and How It Works
Understanding Claude Code's architecture is essential for using it effectively. The system follows a straightforward but powerful pattern: a CLI frontend communicates with the Claude API, executes tool calls locally, and manages an agentic loop that continues until the task is complete.
The Agentic Loop in Detail
When you send a message to Claude Code, the following sequence occurs. First, the system prompt is constructed. This includes the base system prompt from Anthropic, any project-level instructions from CLAUDE.md files, any user-level instructions from ~/.claude/CLAUDE.md, the list of available tools with their descriptions and schemas, the current working directory, the current git status, and the conversation history. The complete system prompt is then sent to the Claude API along with your message.
Claude processes the request and generates a response. This response can contain one or more of the following: plain text to display to the user, tool_use blocks that request the execution of specific tools, or stop_reason indicating the model has finished processing. If tool_use blocks are present, Claude Code executes each tool call, captures the output, and sends the results back to the API as a new message in the conversation. This loop continues until the model generates a response with no tool calls and a stop_reason of "end_turn".
The tool execution happens locally on your machine. When Claude Code requests a file read, it reads the file from your filesystem. When it requests a bash command, it executes that command in your shell. This is a critical architectural decision: the model never has direct access to your filesystem or shell. Instead, it generates structured tool call requests, and the Claude Code runtime acts as a trusted intermediary that validates permissions and executes those requests.
// Conceptual model of the Claude Code agentic loop
public class AgenticLoop
{
private readonly IAnthropicClient _client;
private readonly IToolExecutor _toolExecutor;
private readonly IPolicyEngine _policyEngine;
public async Task<AgentResponse> RunAsync(
string userMessage, CancellationToken ct)
{
var messages = new List<Message>
{
new Message { Role = "user", Content = userMessage }
};
while (true)
{
var response = await _client.CreateMessageAsync(
new MessageRequest
{
Model = "claude-sonnet-4-20250514",
MaxTokens = 16384,
System = await BuildSystemPromptAsync(),
Messages = messages,
Tools = GetToolDefinitions()
}, ct);
if (response.StopReason == "end_turn")
return new AgentResponse
{
Text = response.GetTextContent(),
ToolCallsUsed = response.ToolCallCount
};
var toolResults = new List<ToolResult>();
foreach (var toolCall in response.GetToolCalls())
{
if (await _policyEngine.RequiresApprovalAsync(toolCall))
await PromptUserForApprovalAsync(toolCall);
var result = await _toolExecutor.ExecuteAsync(toolCall);
toolResults.Add(result);
}
messages.Add(response.AsAssistantMessage());
messages.Add(new Message
{
Role = "user",
Content = toolResults.AsContentBlocks()
});
}
}
}
Permission Flow
Every tool call goes through a permission check before execution. The permission model operates in three tiers. The first tier is "allowlist" — tools that are always permitted without user confirmation, such as reading files and searching. The second tier is "ask" — tools that require user confirmation on first use but can be auto-approved for the session, such as writing to files or running non-destructive commands. The third tier is "deny" — tools that always require explicit user confirmation, such as executing potentially destructive shell commands.
This tiered permission model is what makes Claude Code safe to use in production environments. You can configure it to be fully interactive (ask for every action) or fully autonomous (auto-approve everything), with various gradations in between. The configuration can be set per-project, per-user, or per-session.
// Permission evaluation logic
public class PermissionEngine
{
private readonly PermissionConfig _config;
public async Task<PermissionDecision> EvaluateAsync(
ToolCall toolCall, string projectRoot)
{
// Check deny list first (highest priority)
if (_config.DenyList.Any(pattern =>
MatchesPattern(toolCall, pattern)))
{
return PermissionDecision.Deny(
"This tool call matches a deny rule");
}
// Check allow list (lowest priority, auto-approve)
if (_config.AllowList.Any(pattern =>
MatchesPattern(toolCall, pattern)))
{
return PermissionDecision.Allow();
}
// Check scoped permissions (project-level CLAUDE.md)
var projectPolicy = await LoadProjectPolicyAsync(
projectRoot);
if (projectPolicy.TryGetDecision(toolCall, out var decision))
return decision;
// Default: ask the user
return PermissionDecision.Ask(
$"Allow {toolCall.ToolName}?");
}
}
4. Installation and Setup
Claude Code is distributed as an npm package and requires Node.js 18 or later. The installation process is straightforward, but there are several configuration steps that are important for production use.
Prerequisites
- Node.js 18 or later (recommended: Node.js 20 LTS)
- An Anthropic API key (obtained from console.anthropic.com)
- Git installed and configured
- A terminal emulator (iTerm2, Windows Terminal, GNOME Terminal, or VS Code integrated terminal)
Installation
# Install globally via npm
npm install -g @anthropic-ai/claude-code
# Verify installation
claude --version
# Set your API key
export ANTHROPIC_API_KEY="sk-ant-..."
# Navigate to your project and launch Claude Code
cd /path/to/your/project
claude
Initial Configuration
On first launch, Claude Code will walk you through a configuration wizard. The key decisions at this stage are the default model (Claude Sonnet for speed or Claude Opus for complexity), the permission level (interactive, auto-approve reads, or fully autonomous), and whether to enable extended thinking. You can change all of these later.
# Launch with specific model
claude --model claude-opus-4-20250514
# Launch in non-interactive mode (for scripting)
claude --print "Explain this codebase"
# Launch with MCP server
claude --mcp-server my-server -- npx my-mcp-server
# Reset all permissions
claude --reset-permissions
Configuration File Locations
| File | Location | Purpose |
|---|---|---|
| Project CLAUDE.md | ./CLAUDE.md | Project-specific instructions, committed to repo |
| User CLAUDE.md | ~/.claude/CLAUDE.md | User-level instructions, applies to all projects |
| Settings | ~/.claude/settings.json | Global settings (model, permissions, theme) |
| Project Settings | .claude/settings.json | Project-level settings, committed to repo |
| Local Settings | .claude/settings.local.json | Project-level local overrides, gitignored |
| MCP Config | ~/.claude/mcp.json | MCP server configurations |
The hierarchy of configuration is important: project-level settings override user-level settings, and local settings override project settings. This allows teams to share a baseline configuration through the committed CLAUDE.md and .claude/settings.json files while letting individual developers customize their local experience.
5. Core Features — File Editing, Bash Execution, and Search
Claude Code provides six primary built-in tools that together give it complete access to your development environment. Understanding how each tool works, its strengths, and its limitations is essential for effective use.
File Reading
The Read tool allows Claude Code to read any file on your filesystem. It supports reading entire files or specific line ranges. When reading large files, it uses offset and limit parameters to avoid loading entire files into context. The tool returns content with line numbers for precise referencing.
// Claude Code internally manages file reading like this
public class FileReadTool : ITool
{
public string Name => "Read";
public async Task<ToolResult> ExecuteAsync(
ToolInput input, CancellationToken ct)
{
var filePath = ResolvePath(input.FilePath);
ValidateAccess(filePath);
var content = await File.ReadAllLinesAsync(
filePath, ct);
if (input.Offset.HasValue || input.Limit.HasValue)
{
var offset = input.Offset ?? 0;
var limit = input.Limit ?? content.Length;
content = content.Skip(offset)
.Take(limit).ToArray();
}
return ToolResult.Success(
string.Join("\n", content.Select(
(line, i) => $"{i + 1 + (input.Offset ?? 0)}: {line}")));
}
}
File Writing and Editing
The Write tool creates new files or overwrites existing ones entirely. The Edit tool performs surgical string replacements within existing files, which is the preferred method for modifying code because it preserves surrounding context. The Edit tool requires an exact match of the old string, which prevents accidental modifications. If the old string appears multiple times, you must provide more surrounding context to make the match unique.
Bash Execution
The Bash tool executes shell commands in your terminal. This is the most powerful and dangerous tool in Claude Code's arsenal. It can run build commands, execute tests, install packages, start servers, and perform any operation your shell can perform. Commands can be chained using the appropriate shell syntax, and the tool supports configurable timeouts for long-running operations.
# Claude Code automatically runs commands like these:
dotnet build --no-restore
dotnet test --filter "FullyQualifiedName~UserServiceTests"
git status
git diff --staged
docker compose up -d
npm run lint
python -m pytest tests/ -v
Search Tools
Claude Code provides two search tools: Glob for finding files by pattern matching, and Grep for searching file contents using regular expressions. Glob supports standard glob patterns like **/*.cs or src/**/*.ts. Grep supports full regex syntax and can be scoped to specific file types. These tools are used extensively during the explore phase of agentic tasks to understand the codebase structure before making changes.
Tool Capabilities Summary
| Tool | Primary Use | Risk Level | Default Permission |
|---|---|---|---|
| Read | Read file contents | Low | Always allowed |
| Write | Create or overwrite files | Medium | Ask on first use |
| Edit | Surgical string replacement in files | Medium | Ask on first use |
| Bash | Execute shell commands | High | Ask on first use |
| Glob | Find files by pattern | Low | Always allowed |
| Grep | Search file contents with regex | Low | Always allowed |
6. Multi-Turn Agentic Workflow
The multi-turn agentic workflow is what separates Claude Code from simple code completion tools. When you give Claude Code a task, it does not just generate code and stop. It enters a loop of reading, planning, executing, and reflecting that continues until the task is complete. Understanding this workflow is critical for writing effective prompts and setting realistic expectations.
In this example, Claude Code performs nine tool calls across multiple iterations. It reads existing code to understand the architecture, generates new code to implement the feature, modifies configuration to wire everything together, runs the build to verify compilation, runs tests to verify correctness, reads the test output, fixes the failing tests, and runs the tests again to confirm everything passes. This entire workflow happens autonomously after a single natural language instruction.
The key insight is that Claude Code treats each tool call's output as new information that informs its next action. If a build fails, it reads the error messages and fixes the issues. If tests fail, it reads the stack traces and modifies the test or the code. If a grep search returns unexpected results, it refines its search query. This reflective capability — the ability to observe the result of an action and adjust accordingly — is the hallmark of agentic behavior.
Planning and Reasoning
Before executing a complex task, Claude Code often generates a plan. This plan outlines the files it will need to read, the changes it will need to make, the commands it will need to run, and the expected outcomes. With extended thinking enabled, you can see this planning process in real-time as the model reasons through the task. The planning phase is especially valuable for complex refactoring tasks, multi-file feature additions, and architectural changes where the order of operations matters.
// How Claude Code manages conversation state
public class ConversationManager
{
private readonly List<Message> _messages = new();
private readonly ITokenCounter _tokenCounter;
private readonly IContextCompressor _compressor;
private readonly int _maxContextTokens;
public async Task<Message> AddUserMessageAsync(
string content)
{
_messages.Add(new Message
{
Role = "user",
Content = content
});
// Check if context window is getting full
var totalTokens = _tokenCounter.Count(_messages);
if (totalTokens > _maxContextTokens * 0.85)
{
// Compress older messages to free up context
await CompressContextAsync();
}
return _messages.Last();
}
private async Task CompressContextAsync()
{
// Summarize older tool results and conversations
// while preserving recent context in full
var (toSummarize, toKeep) = SplitMessages(
_messages, cutoffIndex: _messages.Count - 6);
var summary = await _compressor.SummarizeAsync(
toSummarize);
_messages.Clear();
_messages.Add(new Message
{
Role = "assistant",
Content = $"[Previous context summary: {summary}]"
});
_messages.AddRange(toKeep);
}
}
7. Tool Use and Function Calling
Tool use is the mechanism by which Claude Code interacts with the outside world. When the Claude model determines it needs to perform an action — read a file, run a command, search for content — it generates a tool_use block in its response. Claude Code's runtime intercepts these blocks, validates them against the permission policy, executes the tool, and returns the result to the model.
Each tool is defined by a JSON schema that specifies its name, description, and input parameters. These definitions are included in the system prompt so the model knows what tools are available and how to invoke them. The model generates tool calls that conform to these schemas, and Claude Code validates the input before execution.
// Tool definition schema used by Claude Code
public class ToolDefinition
{
public string Name { get; set; }
public string Description { get; set; }
public JsonSchema InputSchema { get; set; }
}
// Example: defining a custom tool for MCP integration
var searchTool = new ToolDefinition
{
Name = "search_knowledge_base",
Description = "Search the team's knowledge base for " +
"design decisions, architecture docs, and runbooks",
InputSchema = new JsonSchema
{
Type = "object",
Properties = new Dictionary<string, JsonSchema>
{
["query"] = new JsonSchema
{
Type = "string",
Description = "Search query"
},
["category"] = new JsonSchema
{
Type = "string",
Enum = new[] { "architecture", "runbook",
"decision", "tutorial" },
Description = "Filter by document category"
},
["maxResults"] = new JsonSchema
{
Type = "integer",
Description = "Maximum results to return",
Default = 5
}
},
Required = new[] { "query" }
}
};
// Tool result returned to the model
var toolResult = new ToolResult
{
ToolUseId = "toolu_01A09q90qw90lq917835lq9",
Content = new List<ContentBlock>
{
new TextBlock
{
Text = "Found 3 results:\n" +
"1. Rate Limiting Design Doc (2025-03-15)\n" +
"2. API Gateway Architecture (2025-01-20)\n" +
"3. Auth Middleware Runbook (2025-06-10)"
}
}
};
Parallel Tool Calls
Claude Code can issue multiple tool calls in a single response. When the model determines that several actions are independent of each other — for example, reading multiple files simultaneously — it can generate multiple tool_use blocks that are executed in parallel. This significantly reduces the total number of API round-trips and speeds up task completion. The parallel execution is managed by Claude Code's runtime, which batches independent tool calls and executes them concurrently.
Tool Result Processing
Tool results are processed and returned to the model as user messages. This is an important architectural detail: tool results appear as user messages in the API conversation, not as assistant messages. This convention comes from the Anthropic API design where tool results are part of the user turn. The model then processes these results and decides what to do next — whether to make another tool call, generate a text response, or signal that the task is complete.
8. Context Window Management
Context window management is one of the most critical and sophisticated aspects of Claude Code. The Claude 3.5 Sonnet model has a 200,000 token context window, and Claude Opus has the same capacity. However, real-world codebases can easily exceed this limit when you account for source code, test files, configuration, build outputs, error messages, and conversation history. Claude Code employs several strategies to work within these constraints.
Strategic File Loading
When Claude Code begins a task, it does not load the entire codebase into context. Instead, it uses Glob and Grep to identify the relevant files, reads only the portions it needs, and progressively builds context. For example, when asked to fix a bug, it might start by searching for the relevant class name, reading the file that contains it, reading the test file, reading the error message, and only then forming a plan. This incremental approach minimizes the amount of context consumed by irrelevant code.
// Context budget management
public class ContextBudgetManager
{
private const int SystemPromptTokens = 4000;
private const int ConversationReserve = 8000;
private const double ToolResultBudget = 0.3; // 30% of context
public FileReadRange CalculateOptimalReadRange(
string filePath, int totalLines,
ContextWindow window)
{
var remaining = window.AvailableTokens
- SystemPromptTokens
- ConversationReserve;
var linesBudget = (int)(remaining * ToolResultBudget
/ TokensPerLine(filePath));
// Prefer reading targeted ranges over full files
if (totalLines <= linesBudget)
{
return new FileReadRange
{
FilePath = filePath,
Offset = 0,
Limit = totalLines
};
}
// For large files, read the most relevant section
return new FileReadRange
{
FilePath = filePath,
Offset = 0,
Limit = Math.Min(linesBudget, totalLines)
};
}
}
Context Compression
When the conversation history grows large, Claude Code compresses older messages by summarizing tool results and conversation turns. A tool result that originally contained the full content of a 500-line file might be compressed to a one-sentence summary: "Read Program.cs — contains ASP.NET Core startup configuration with dependency injection, middleware pipeline, and controller registration." This preserves the semantic meaning while freeing up tokens for new work.
Strategies Overview
| Strategy | When Used | Impact |
|---|---|---|
| Incremental file reading | Large files | Read only relevant lines |
| Tool result compression | Context approaching limit | Summarize older tool outputs |
| Conversation summarization | Long conversations | Condense older turns into summaries |
| Selective search scoping | Large codebases | Narrow searches by file type, directory |
| File path references | After initial read | Reference files by path instead of content |
The context management system is transparent to the user in most cases, but understanding it helps you write more effective prompts. For example, instead of asking Claude Code to "fix the bug," providing the specific file path, function name, and error message reduces the amount of context the model needs to spend on exploration and leaves more room for reasoning and code generation.
9. System Prompt Engineering
The system prompt is the foundation of Claude Code's behavior. It is a carefully crafted instruction set that tells the model what it is, what tools it has, how to use them, and what constraints to follow. Understanding the system prompt is important for two reasons: first, it helps you understand why Claude Code behaves the way it does, and second, it helps you write CLAUDE.md instructions that complement the system prompt effectively.
System Prompt Components
The system prompt is constructed from multiple sources merged together. The base prompt from Anthropic establishes Claude Code's identity, available tools, and behavioral guidelines. The project CLAUDE.md adds project-specific instructions like coding conventions, architecture decisions, and testing requirements. The user CLAUDE.md adds personal preferences. The tool definitions are appended with their full JSON schemas. Finally, contextual information like the current directory, git status, and OS details are included.
// System prompt construction
public class SystemPromptBuilder
{
public async Task<string> BuildAsync(
ProjectContext context)
{
var sb = new StringBuilder();
// Base prompt (from Anthropic)
sb.AppendLine(await LoadBasePromptAsync());
// Tool definitions
sb.AppendLine("\n## Available Tools");
foreach (var tool in _toolRegistry.GetAllTools())
{
sb.AppendLine($"\n### {tool.Name}");
sb.AppendLine(tool.Description);
sb.AppendLine(
$"Input schema: {JsonSerializer.Serialize(tool.InputSchema)}");
}
// Project CLAUDE.md
var projectInstructions = await LoadIfExistsAsync(
Path.Combine(context.ProjectRoot, "CLAUDE.md"));
if (projectInstructions != null)
{
sb.AppendLine("\n## Project Instructions");
sb.AppendLine(projectInstructions);
}
// User CLAUDE.md
var userInstructions = await LoadIfExistsAsync(
Path.Combine(
Environment.GetFolderPath(
Environment.SpecialFolder.UserProfile),
".claude", "CLAUDE.md"));
if (userInstructions != null)
{
sb.AppendLine("\n## User Preferences");
sb.AppendLine(userInstructions);
}
// Context
sb.AppendLine($"\n## Context");
sb.AppendLine($"Working directory: {context.WorkingDir}");
sb.AppendLine($"OS: {context.OsType}");
sb.AppendLine($"Git branch: {context.CurrentBranch}");
return sb.ToString();
}
}
Writing Effective CLAUDE.md Instructions
The CLAUDE.md file is your primary mechanism for customizing Claude Code's behavior. The best CLAUDE.md files are specific, actionable, and concise. Instead of writing vague instructions like "write clean code," provide concrete rules: "Use the repository pattern for data access. All services must implement an interface. Use dependency injection via the Microsoft.Extensions.DependencyInjection container. All public methods must have XML documentation."
Effective CLAUDE.md files typically include the project architecture overview, coding conventions, naming conventions, testing requirements, preferred libraries and frameworks, file organization patterns, and any anti-patterns to avoid. They should also include information about the build and test commands so Claude Code can verify its own work.
Example CLAUDE.md for a .NET Project
# Project: PaymentGateway
## Architecture
- Clean Architecture with Domain, Application, Infrastructure, API layers
- CQRS pattern using MediatR for command/query separation
- Entity Framework Core for data access with PostgreSQL
- Redis for caching and distributed locks
## Coding Conventions
- All services implement interfaces suffixed with "Service"
- Use records for DTOs, classes for domain entities
- Always use async/await for I/O operations
- Never use Task.Result or Task.Wait() — always await
- Use primary constructors where appropriate (.NET 12+)
## Testing
- Unit tests: xUnit with Moq for mocking
- Integration tests: Testcontainers for PostgreSQL and Redis
- Run: `dotnet test --filter "Category!=Integration"`
- All new code must have corresponding unit tests
## Build & Deploy
- Build: `dotnet build --no-restore`
- Test: `dotnet test`
- Lint: `dotnet format --verify-no-changes`
- Never modify .github/workflows/ without explicit approval
10. Custom Instructions with CLAUDE.md
CLAUDE.md files are the primary extensibility mechanism for Claude Code. They function as persistent instructions that are loaded into the system prompt every time Claude Code starts a conversation in the directory containing the file. Understanding the CLAUDE.md hierarchy, the available directives, and best practices for writing effective instructions is essential for productive use.
The CLAUDE.md Hierarchy
Claude Code loads CLAUDE.md files from three locations in a specific order. First, it loads the user-level CLAUDE.md from ~/.claude/CLAUDE.md, which applies to all projects. Second, it loads the project-level CLAUDE.md from ./CLAUDE.md in the project root. Third, it loads directory-level CLAUDE.md files from subdirectories. Later instructions can override earlier ones, and instructions closer to the relevant code take precedence.
// CLAUDE.md loading and merging
public class ClaudeMdLoader
{
private readonly string _projectRoot;
public async Task<string> LoadAllInstructionsAsync()
{
var instructions = new List<string>();
// 1. User-level (lowest priority)
var userMd = await LoadFileIfExistsAsync(
Path.Combine(
Environment.GetFolderPath(
Environment.SpecialFolder.UserProfile),
".claude", "CLAUDE.md"));
if (userMd != null)
instructions.Add($"[User]\n{userMd}");
// 2. Project root
var projectMd = await LoadFileIfExistsAsync(
Path.Combine(_projectRoot, "CLAUDE.md"));
if (projectMd != null)
instructions.Add($"[Project]\n{projectMd}");
// 3. Directory-level files
var directoryMds = Directory.GetFiles(
_projectRoot, "CLAUDE.md",
SearchOption.AllDirectories);
foreach (var md in directoryMds
.Where(f => f != Path.Combine(
_projectRoot, "CLAUDE.md")))
{
var relativePath = Path.GetRelativePath(
_projectRoot, Path.GetDirectoryName(md));
var content = await File.ReadAllTextAsync(md);
instructions.Add(
$"[Directory: {relativePath}]\n{content}");
}
return string.Join("\n\n", instructions);
}
}
CLAUDE.md Best Practices
- Keep instructions concise — every token in CLAUDE.md is loaded into context
- Be specific and actionable — "Use repository pattern" instead of "Follow best practices"
- Include build and test commands so Claude Code can verify its work
- Document the architecture at a high level so Claude Code understands the big picture
- Specify what NOT to do — "Never modify migration files directly"
- Use section headers to organize instructions by category
- Update CLAUDE.md as the project evolves — it is a living document
- Commit project-level CLAUDE.md to the repository so the whole team benefits
11. Permissions and Security Model
The permissions and security model is what makes Claude Code safe to use in production environments. Every tool call is evaluated against a set of permission rules before execution. The model never has direct access to your filesystem or shell — it generates structured tool call requests, and the Claude Code runtime acts as a trusted gatekeeper.
Permission Tiers
| Tier | Behavior | Tools | Configuration |
|---|---|---|---|
| Auto-allow | Execute without asking | Read, Glob, Grep | Always on |
| Session-allow | Ask first time, then auto-approve for session | Write, Edit | Configurable |
| Ask-every-time | Always ask for confirmation | Bash (non-destructive) | Default for bash |
| Deny | Never execute | User-defined patterns | Configurable |
Permission Configuration
// .claude/settings.json — project-level permissions
{
"permissions": {
"allow": [
"Bash(dotnet build)",
"Bash(dotnet test*)",
"Bash(git status)",
"Bash(git diff*)",
"Bash(git log*)",
"Write(src/**/*.cs)",
"Edit(src/**/*.cs)"
],
"deny": [
"Bash(rm -rf*)",
"Bash(git push --force*)",
"Bash(git reset --hard*)",
"Write(.github/workflows/*)",
"Bash(*--destructive*)"
]
}
}
// Permission pattern matching engine
public class PermissionPatternMatcher
{
public bool Matches(string toolName, string toolInput,
string pattern)
{
// Pattern format: "ToolName(arguments pattern)"
var match = Regex.Match(pattern,
@"^(\w+)\((.+)\)$");
if (!match.Success) return false;
var patternTool = match.Groups[1].Value;
var patternArgs = match.Groups[2].Value;
if (!toolName.Equals(patternTool,
StringComparison.OrdinalIgnoreCase))
return false;
// Convert glob pattern to regex
var regexPattern = "^" +
Regex.Escape(patternArgs)
.Replace("\\*\\*", ".*")
.Replace("\\*", "[^/]*") +
"$";
return Regex.IsMatch(toolInput, regexPattern,
RegexOptions.IgnoreCase);
}
}
Enterprise Security Considerations
For enterprise deployments, Claude Code supports several additional security features. The settings.json file can be locked down by administrators so that developers cannot override security policies. Network restrictions can limit which MCP servers can be connected. The deny list can be made non-overridable to prevent accidental or intentional execution of dangerous commands. All tool calls can be logged to an audit trail for compliance purposes.
12. MCP — Model Context Protocol Integration
The Model Context Protocol (MCP) is an open protocol developed by Anthropic that allows Claude Code to connect to external tool servers. MCP servers extend Claude Code's capabilities beyond its built-in tools by providing access to databases, APIs, documentation systems, cloud services, and custom tools. MCP is to Claude Code what plugins are to an IDE — a standardized way to extend functionality.
MCP Server Architecture
An MCP server is a standalone process that communicates with Claude Code over the Model Context Protocol. The protocol supports three types of capabilities: tools (functions the model can call), resources (data the model can read), and prompts (pre-defined prompt templates). Each capability is described by a JSON schema that Claude Code includes in its tool definitions.
// Example MCP server in C# using the official SDK
using ModelContextProtocol.Server;
using ModelContextProtocol.Server.Transport;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMcpServer()
.WithStdioServerTransport()
.WithTool<SearchKnowledgeBaseTool>()
.WithTool<CreateJiraTicketTool>()
.WithTool<QueryDatabaseTool>()
.WithResource<ArchitectureDocsResource>();
var app = builder.Build();
await app.RunAsync();
// Tool implementation
[McpTool("search_knowledge_base",
"Search the team knowledge base for documents")]
public class SearchKnowledgeBaseTool : IMcpTool
{
private readonly IKnowledgeBaseClient _kb;
public async Task<McpToolResult> ExecuteAsync(
SearchKnowledgeBaseInput input)
{
var results = await _kb.SearchAsync(
input.Query,
input.Category,
input.MaxResults);
return new McpToolResult
{
Content = results.Select(r => new TextContent
{
Text = $"## {r.Title}\n{r.Summary}\n" +
$"URL: {r.Url}\n" +
$"Last updated: {r.LastUpdated:yyyy-MM-dd}"
}).ToList()
};
}
}
[McpTool("create_jira_ticket",
"Create a Jira ticket for tracking work")]
public class CreateJiraTicketTool : IMcpTool
{
private readonly IJiraClient _jira;
public async Task<McpToolResult> ExecuteAsync(
CreateJiraTicketInput input)
{
var ticket = await _jira.CreateTicketAsync(
new CreateTicketRequest
{
Project = input.Project,
Summary = input.Summary,
Description = input.Description,
IssueType = input.IssueType ?? "Task",
Priority = input.Priority ?? "Medium"
});
return new McpToolResult
{
Content = new List<Content>
{
new TextContent
{
Text = $"Created {ticket.Key}: " +
$"{ticket.Url}"
}
}
};
}
}
MCP Configuration
// ~/.claude/mcp.json
{
"mcpServers": {
"knowledge-base": {
"command": "npx",
"args": ["-y", "@company/knowledge-base-mcp"],
"env": {
"KB_API_KEY": "${KB_API_KEY}"
}
},
"jira": {
"command": "dotnet",
"args": ["run", "--project", "./tools/jira-mcp"],
"env": {
"JIRA_URL": "https://company.atlassian.net",
"JIRA_TOKEN": "${JIRA_TOKEN}"
}
},
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
"env": {
"DATABASE_URL": "${DATABASE_URL}"
}
}
}
}
Popular MCP Servers
| MCP Server | Purpose | Key Capabilities |
|---|---|---|
| Postgres MCP | Database access | Query, schema inspection, table management |
| GitHub MCP | GitHub integration | Issues, PRs, repos, code search |
| Filesystem MCP | Extended file access | Sandboxed file operations |
| Puppeteer MCP | Browser automation | Screenshots, page interaction, scraping |
| Slack MCP | Team communication | Send messages, read channels |
| Docker MCP | Container management | List, inspect, run containers |
13. GitHub Actions and CI/CD Integration
One of Claude Code's most powerful capabilities is its integration with GitHub Actions. You can configure GitHub to automatically run Claude Code on pull requests, issues, and other events. This enables automated code review, automated test generation, automated documentation updates, and even automated bug fixes — all powered by Claude's agentic capabilities running in a CI/CD environment.
# .github/workflows/claude-code.yml
name: Claude Code Review
on:
pull_request:
types: [opened, synchronize]
permissions:
contents: read
pull-requests: write
issues: write
jobs:
claude-review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
claude_code_args: "--model claude-sonnet-4-20250514"
prompt: |
Review this pull request and provide feedback on:
1. Code quality and adherence to project conventions
2. Potential bugs or edge cases
3. Test coverage gaps
4. Performance concerns
5. Security vulnerabilities
Read the CLAUDE.md file first to understand project conventions.
allowed_tools: "Read,Glob,Grep"
Automated Code Review Architecture
// CI/CD integration service for automated code review
public class CICDCodeReviewService
{
private readonly IAnthropicClient _claude;
private readonly IGitHubClient _github;
public async Task<CodeReviewResult> ReviewPullRequestAsync(
int prNumber, string repository)
{
// Fetch PR details
var pr = await _github.GetPullRequestAsync(
repository, prNumber);
var diff = await _github.GetPullRequestDiffAsync(
repository, prNumber);
var changedFiles = await _github
.GetPullRequestFilesAsync(repository, prNumber);
// Load project conventions
var claudeMd = await File.ReadAllTextAsync(
"CLAUDE.md");
// Build review prompt
var prompt = $"""
Review pull request #{prNumber}: {pr.Title}
## Project Conventions
{claudeMd}
## Diff
{diff}
## Changed Files
{string.Join("\n", changedFiles.Select(f =>
$"{f.Filename} (+{f.Additions} -{f.Deletions})"))}
Provide a structured review covering:
1. Bugs and potential issues
2. Code style violations
3. Missing test coverage
4. Performance concerns
5. Security vulnerabilities
""";
var response = await _claude.CreateMessageAsync(
prompt, model: "claude-sonnet-4-20250514");
// Post review to GitHub
await _github.CreatePullRequestReviewAsync(
repository, prNumber,
new PullRequestReview
{
Body = response.Text,
Event = response.HasIssues
? PullRequestEvent.RequestChanges
: PullRequestEvent.Approve
});
return new CodeReviewResult
{
PrNumber = prNumber,
Approved = !response.HasIssues,
Comments = response.ReviewComments
};
}
}
GitHub Actions Use Cases
| Use Case | Trigger | Claude Code Action |
|---|---|---|
| Code Review | Pull request opened | Review diff, post comments, approve/request changes |
| Auto-fix Lint Errors | Pull request opened | Read lint errors, fix code, push commit |
| Test Generation | Pull request merged | Analyze changes, generate missing tests, open PR |
| Documentation Update | Push to main | Read code changes, update relevant docs |
| Issue Triage | Issue opened | Analyze issue, label it, suggest assignee |
| Dependency Audit | Scheduled (weekly) | Scan dependencies, check for vulnerabilities |
14. Hooks and Automation
Hooks are a powerful feature that allow you to run custom scripts at specific points in Claude Code's lifecycle. Hooks enable automated formatting after file edits, custom validation before command execution, notification sending when tasks complete, and integration with external tools that are not covered by MCP servers. They function similarly to git hooks but operate within Claude Code's agentic loop.
Hook Types
| Hook Type | Fires When | Use Case |
|---|---|---|
| PreToolCall | Before any tool executes | Validation, logging, blocking |
| PostToolCall | After any tool executes | Formatting, linting, notifications |
| PreToolCall:Write | Before file write | License header injection |
| PostToolCall:Edit | After file edit | Auto-format with dotnet format |
| PostToolCall:Bash | After bash command | Parse output, trigger alerts |
// .claude/hooks.json
{
"hooks": {
"PostToolCall": [
{
"matcher": "Edit",
"pattern": "*.cs",
"command": "dotnet format $CLAUDE_FILE_PATH"
},
{
"matcher": "Edit",
"pattern": "*.ts",
"command": "npx prettier --write $CLAUDE_FILE_PATH"
},
{
"matcher": "Bash",
"pattern": "dotnet test*",
"command": "echo 'Tests completed at $(date)' >> .claude/test-log.txt"
}
],
"PreToolCall": [
{
"matcher": "Bash",
"pattern": "git push*",
"command": "echo 'WARNING: Pushing to remote' >&2"
}
]
}
}
// Hook execution engine
public class HookEngine
{
private readonly List<HookDefinition> _hooks;
private readonly IProcessRunner _processRunner;
public async Task<HookResult> ExecuteHooksAsync(
string hookType, ToolCall toolCall)
{
var matchingHooks = _hooks
.Where(h => h.Type == hookType)
.Where(h => MatchesTool(h, toolCall))
.Where(h => MatchesPattern(h, toolCall));
var results = new List<HookExecutionResult>();
foreach (var hook in matchingHooks)
{
var environment = new Dictionary<string, string>
{
["CLAUDE_FILE_PATH"] =
toolCall.Input.FilePath ?? "",
["CLAUDE_TOOL_NAME"] = toolCall.ToolName,
["CLAUDE_TOOL_INPUT"] =
JsonSerializer.Serialize(toolCall.Input)
};
var result = await _processRunner.RunAsync(
hook.Command, environment);
results.Add(new HookExecutionResult
{
HookName = hook.Name,
ExitCode = result.ExitCode,
Output = result.Output,
Success = result.ExitCode == 0
});
}
return new HookResult
{
AllPassed = results.All(r => r.Success),
Results = results
};
}
}
15. Git Workflow Integration
Claude Code has deep integration with git, enabling it to understand your repository history, create branches, stage changes, commit with meaningful messages, and create pull requests. This git awareness is not superficial — Claude Code reads git diffs to understand what has changed, checks git blame to understand who wrote what, and uses git log to understand the project's evolution.
Git Operations Claude Code Can Perform
- Read-only: git status, git log, git diff, git blame, git show
- Branch management: git checkout -b, git branch, git switch
- Staging and committing: git add, git commit with conventional messages
- PR creation: Via GitHub CLI (gh pr create) or MCP server
- Conflict resolution: Can read merge conflicts and help resolve them
// Git-aware task execution
public class GitAwareWorkflow
{
private readonly IShellExecutor _shell;
public async Task<WorkBranch> CreateFeatureBranchAsync(
string featureName)
{
// Ensure clean working tree
var status = await _shell.ExecuteAsync("git status --porcelain");
if (!string.IsNullOrWhiteSpace(status.Output))
throw new WorkingTreeDirtyException(
"Working tree must be clean before creating a feature branch");
// Create and switch to feature branch
var branchName = $"feature/{featureName.ToLower()
.Replace(" ", "-")}";
await _shell.ExecuteAsync(
$"git checkout -b {branchName}");
return new WorkBranch
{
Name = branchName,
CreatedAt = DateTime.UtcNow
};
}
public async Task<CommitResult> CommitWithMessageAsync(
string message, string[] files)
{
// Stage specific files
foreach (var file in files)
{
await _shell.ExecuteAsync(
$"git add \"{file}\"");
}
// Create commit with conventional commit format
var result = await _shell.ExecuteAsync(
$"git commit -m \"{message}\"");
// Extract commit hash
var hash = await _shell.ExecuteAsync(
"git rev-parse --short HEAD");
return new CommitResult
{
Hash = hash.Output.Trim(),
Message = message,
FilesChanged = files.Length
};
}
}
Conventional Commit Messages
Claude Code automatically generates conventional commit messages that follow the format type(scope): description. The types include feat, fix, refactor, docs, test, chore, and perf. This consistency is valuable for automated changelog generation, semantic versioning, and maintaining a clean git history.
16. Comparing Claude Code vs Cursor vs GitHub Copilot
The AI coding tool landscape is crowded with options, and understanding how Claude Code compares to its main competitors — Cursor and GitHub Copilot — is essential for making informed decisions about which tool to use. Each tool has a fundamentally different philosophy, architecture, and sweet spot.
Feature Comparison Matrix
| Feature | Claude Code | Cursor | GitHub Copilot |
|---|---|---|---|
| Interface | Terminal CLI | VS Code fork | IDE plugin |
| Primary Model | Claude Sonnet/Opus | GPT-4, Claude, custom | GPT-4o, Claude |
| Agentic Capabilities | Full agentic loop | Agent mode | Copilot Workspace |
| File System Access | Full (with permissions) | Within workspace | Within workspace |
| Shell Access | Full (with permissions) | Limited | Very limited |
| Git Integration | Full (branch, commit, PR) | Basic | PR summaries |
| MCP Support | Native | Yes | Limited |
| CI/CD Integration | GitHub Actions | None | GitHub Actions |
| Context Window | 200K tokens | Varies by model | Varies by model |
| Price (Individual) | Pay-per-token (API) | $20/month | $10/month |
| Offline Capability | No | No | No |
| Language Support | All languages | All languages | All languages |
| Custom Instructions | CLAUDE.md | .cursorrules | Custom instructions |
| Multi-repo Support | Yes | Limited | No |
| Enterprise Controls | Yes (settings.json locks) | Yes (team settings) | Yes (org policies) |
When to Choose Claude Code
Claude Code is the best choice when you need maximum autonomy and flexibility. Its terminal-native approach means it works with any IDE, any project structure, and any workflow. The full shell access enables it to run builds, tests, and deployment scripts. The agentic loop allows it to handle complex multi-step tasks that require reading, writing, testing, and iterating. The MCP extensibility means you can add custom tools for any system in your stack.
When to Choose Cursor
Cursor is the best choice when you prefer a tightly integrated IDE experience with AI capabilities built into the editor. Its multi-model support lets you choose the best model for each task. The inline editing experience — select code, describe the change, see the diff — is smoother than Claude Code's text-based interaction. The .cursorrules file provides project-specific instructions similar to CLAUDE.md.
When to Choose GitHub Copilot
GitHub Copilot is the best choice for individual developers who want affordable AI-powered autocomplete and chat. At $10/month, it provides excellent value for line-by-line code completion and conversational assistance. Its deep integration with GitHub means it understands your repository context and can summarize PRs and issues effectively.
17. Pricing and Token Usage
Understanding Claude Code's pricing model is essential for managing costs, especially in team and enterprise settings. Unlike subscription-based tools like Cursor and Copilot, Claude Code operates on a pay-per-token basis through the Anthropic API. This means your costs are directly proportional to how much you use the tool and which model you choose.
Model Pricing
| Model | Input Price (per 1M tokens) | Output Price (per 1M tokens) | Context Window | Best For |
|---|---|---|---|---|
| Claude Sonnet 4 | $3.00 | $15.00 | 200K | General coding, most tasks |
| Claude Opus 4 | $15.00 | $75.00 | 200K | Complex reasoning, architecture |
| Claude 3.5 Haiku | $0.80 | $4.00 | 200K | Simple tasks, bulk operations |
Cost Estimation
A typical Claude Code session for a moderately complex task uses approximately 20,000 to 50,000 input tokens and 5,000 to 15,000 output tokens. Using Claude Sonnet 4, this translates to approximately $0.06 to $0.15 per session for inputs and $0.075 to $0.225 for outputs, totaling roughly $0.14 to $0.38 per task. For a developer who completes 20 AI-assisted tasks per day, this works out to $2.80 to $7.60 per day, or approximately $60 to $165 per month.
// Cost tracking service
public class CostTracker
{
private readonly Dictionary<string, ModelPricing> _pricing = new()
{
["claude-sonnet-4-20250514"] = new ModelPricing
{
InputPerMillion = 3.00m,
OutputPerMillion = 15.00m,
CacheWritePerMillion = 3.75m,
CacheReadPerMillion = 0.30m
},
["claude-opus-4-20250514"] = new ModelPricing
{
InputPerMillion = 15.00m,
OutputPerMillion = 75.00m,
CacheWritePerMillion = 18.75m,
CacheReadPerMillion = 1.50m
}
};
public CostEstimate EstimateSessionCost(
string model, int inputTokens, int outputTokens,
int cacheReadTokens = 0, int cacheWriteTokens = 0)
{
var pricing = _pricing[model];
var inputCost = inputTokens / 1_000_000m
* pricing.InputPerMillion;
var outputCost = outputTokens / 1_000_000m
* pricing.OutputPerMillion;
var cacheReadCost = cacheReadTokens / 1_000_000m
* pricing.CacheReadPerMillion;
var cacheWriteCost = cacheWriteTokens / 1_000_000m
* pricing.CacheWritePerMillion;
return new CostEstimate
{
InputCost = inputCost,
OutputCost = outputCost,
CacheReadCost = cacheReadCost,
CacheWriteCost = cacheWriteCost,
TotalCost = inputCost + outputCost
+ cacheReadCost + cacheWriteCost,
Model = model,
EstimatedAt = DateTime.UtcNow
};
}
}
Cost Optimization Strategies
- Use Claude Sonnet for most tasks — it is 5x cheaper than Opus and sufficient for 90% of coding tasks
- Use extended thinking sparingly — it generates additional output tokens
- Write specific prompts — clear instructions reduce the number of exploration tool calls
- Provide file paths and function names in your prompts to minimize search overhead
- Use the
--printflag for non-interactive scripting to avoid conversation overhead - Set token budgets for long-running tasks to prevent runaway costs
18. Best Practices and Tips
Effective use of Claude Code requires understanding not just what the tool can do, but how to communicate with it most efficiently. These best practices are distilled from extensive use across teams and projects of varying sizes.
Prompt Engineering for Claude Code
- Be specific about scope: "Add rate limiting to the /api/orders endpoint" instead of "add rate limiting"
- Provide context: "In the PaymentService class (src/Services/PaymentService.cs), the ProcessPayment method throws an unhandled exception when..."
- Specify constraints: "Use the existing RedisCacheService for storing rate limit counters. Do not introduce new NuGet packages."
- Describe the expected outcome: "After the changes, running `dotnet test --filter PaymentTests` should pass all 15 tests"
- Break large tasks into steps: Instead of one giant prompt, give Claude Code sequential instructions and let it complete each before moving on
Working with Large Codebases
- Start by asking Claude Code to explore and understand the relevant part of the codebase before making changes
- Reference specific files and functions by name to reduce context usage
- Use directory-level CLAUDE.md files to provide context about different parts of the codebase
- For very large changes, break the work into multiple sessions and reference what was done in previous sessions
Verification and Quality
- Always ask Claude Code to run the test suite after making changes
- Ask Claude Code to explain what it changed and why — this helps you verify the reasoning
- Review the diff before accepting changes — Claude Code is not infallible
- Use the
--resumeflag to continue a previous session when iterating on a task
// Example: well-structured Claude Code prompt
// BAD: "Fix the bug in the API"
// GOOD:
"""
There is a bug in the OrderController.GetById endpoint
at src/Controllers/OrderController.cs:47.
When a customer requests an order that belongs to another
customer, the endpoint returns a 500 Internal Server Error
instead of a 403 Forbidden.
The issue is that OrderService.GetOrderByIdAsync does not
check the customerId parameter against the order's
OwnerId before returning the order.
Please:
1. Add an authorization check in OrderService that
compares the requesting customer's ID with the
order's OwnerId
2. Throw an UnauthorizedAccessException if they
don't match
3. Handle this exception in the global error handler
to return 403
4. Add a unit test in OrderServiceTests that verifies
this behavior
5. Run `dotnet test --filter OrderServiceTests` to
verify
"""
19. Common Pitfalls
Even experienced developers fall into traps when using Claude Code. Understanding these pitfalls helps you avoid wasted time and unexpected behavior.
Pitfall 1: Overly Vague Prompts
Providing vague instructions like "improve this code" or "make it better" leads to unpredictable results. Claude Code has to guess what you mean, and its interpretation may not match your intent. Always provide specific, measurable goals.
Pitfall 2: Ignoring Context Window Limits
Asking Claude Code to read and understand an entire large codebase in a single prompt will exhaust the context window. Instead, guide Claude Code through the relevant parts incrementally. Start with the entry point, then follow the code path.
Pitfall 3: Trusting Without Reviewing
Claude Code generates code that looks correct and is syntactically valid, but it can contain subtle logic errors, incorrect assumptions about business rules, or security vulnerabilities. Always review the diff before committing.
Pitfall 4: Not Using CLAUDE.md
Without CLAUDE.md, Claude Code defaults to generic coding patterns that may not match your project's conventions. Investing 30 minutes in writing a good CLAUDE.md saves hours of correcting Claude Code's output.
Pitfall 5: Running Unrestricted Permissions
Setting all tools to auto-approve removes the safety net that prevents accidental destructive operations. At minimum, keep destructive bash commands in the ask-every-time tier.
Pitfall 6: Not Providing Test Commands
If Claude Code does not know how to run your tests, it cannot verify its own work. Always include the test command in your CLAUDE.md or prompt so Claude Code can validate changes.
Pitfall 7: Ignoring Model Selection
Using Claude Opus for simple tasks like renaming a variable wastes money. Using Claude Sonnet for complex architectural decisions may produce suboptimal results. Match the model to the task.
Pitfall 8: Not Using Extended Thinking
For complex tasks that require multi-step reasoning — debugging a race condition, designing a database migration, planning a large refactor — extended thinking significantly improves output quality. Enable it when the task demands careful reasoning.
20. Real-World Use Cases
Claude Code is not a theoretical tool — it is being used in production environments across the industry for a wide range of tasks. These real-world use cases demonstrate the breadth and depth of its capabilities.
Use Case 1: Legacy Codebase Migration
A team needed to migrate a 500,000-line .NET Framework 4.8 application to .NET 8. Claude Code was used to analyze the codebase, identify incompatible APIs, generate migration scripts, update project files, and verify the migration through automated testing. The agent could read the entire solution structure, understand the dependency graph, and make coordinated changes across hundreds of files.
Use Case 2: Automated Bug Triage
A team configured Claude Code in GitHub Actions to automatically triage incoming issues. When a new issue is opened, Claude Code reads the issue description, searches the codebase for related code, checks for duplicate issues, labels the issue with the appropriate component and severity, and suggests potential root causes. This reduced triage time from hours to minutes.
Use Case 3: Security Audit
Claude Code was used to perform a comprehensive security audit of an API gateway. It systematically searched for SQL injection vulnerabilities, hardcoded credentials, insecure cryptography usage, missing input validation, and improper error handling. The audit covered the entire codebase and produced a structured report with file paths and line numbers.
Use Case 4: Test Coverage Improvement
A team used Claude Code to improve test coverage from 45% to 85% over two weeks. Claude Code analyzed the existing test suite, identified untested code paths, generated comprehensive test cases for each, and verified them through the test runner. The agent handled edge cases, boundary conditions, and error scenarios that human testers often overlook.
// Example: Claude Code generating comprehensive tests
public class PaymentServiceTests
{
// Claude Code identified these test scenarios
// by analyzing PaymentService.ProcessPaymentAsync
[Fact]
public async Task ProcessPayment_NullCard_ThrowsArgumentException()
{
// Arrange
var service = new PaymentService(
Mock.Of<IPaymentGateway>(),
Mock.Of<ILogger<PaymentService>>());
// Act & Assert
await Assert.ThrowsAsync<ArgumentNullException>(
() => service.ProcessPaymentAsync(
new PaymentRequest { Card = null }));
}
[Theory]
[InlineData(-1)]
[InlineData(0)]
[InlineData(100_001)]
public async Task ProcessPayment_InvalidAmount_ThrowsArgumentException(
decimal amount)
{
var service = new PaymentService(
Mock.Of<IPaymentGateway>(),
Mock.Of<ILogger<PaymentService>>());
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(
() => service.ProcessPaymentAsync(
new PaymentRequest
{
Card = TestCards.ValidVisa,
Amount = amount,
Currency = "USD"
}));
}
[Fact]
public async Task ProcessPayment_GatewayDeclines_ReturnsFailedResult()
{
var gateway = new Mock<IPaymentGateway>();
gateway.Setup(g => g.ChargeAsync(It.IsAny<ChargeRequest>()))
.ReturnsAsync(new ChargeResponse
{
Success = false,
DeclineCode = "insufficient_funds"
});
var service = new PaymentService(
gateway.Object,
Mock.Of<ILogger<PaymentService>>());
var result = await service.ProcessPaymentAsync(
new PaymentRequest
{
Card = TestCards.ValidVisa,
Amount = 99.99m,
Currency = "USD"
});
Assert.False(result.Success);
Assert.Equal("insufficient_funds", result.DeclineCode);
}
}
Use Case Summary
| Use Case | Time Saved | Model Used | Complexity |
|---|---|---|---|
| Legacy Migration | 2-3 weeks | Claude Opus | High |
| Bug Triage | Hours per day | Claude Sonnet | Medium |
| Security Audit | 3-5 days | Claude Opus | High |
| Test Coverage | 1-2 weeks | Claude Sonnet | Medium |
| Code Review | Hours per PR | Claude Sonnet | Medium |
| Documentation | 2-3 days | Claude Sonnet | Low-Medium |
21. Extending Claude Code with Plugins
Beyond MCP servers, Claude Code supports several extension mechanisms that allow you to customize and extend its behavior. These include custom tool definitions, script-based integrations, and the hooks system we discussed earlier.
Building a Custom MCP Server
The most common way to extend Claude Code is by building a custom MCP server. A custom MCP server gives Claude Code access to any system or API that does not have an existing integration. Common examples include internal documentation systems, monitoring dashboards, deployment pipelines, and proprietary databases.
// Complete MCP server template
using ModelContextProtocol.Server;
using ModelContextProtocol.Server.Transport;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddMcpServer()
.WithStdioServerTransport()
.WithTool<QueryMetricsTool>()
.WithTool<DeployServiceTool>()
.WithTool<GetLogsTool>()
.WithResource<ArchitectureDiagramResource>();
})
.Build();
await host.RunAsync();
// Tool: Query application metrics
[McpTool("query_metrics",
"Query Prometheus metrics for a given service")]
public class QueryMetricsTool : IMcpTool
{
private readonly IPrometheusClient _prometheus;
public QueryMetricsTool(IPrometheusClient prometheus)
{
_prometheus = prometheus;
}
public async Task<McpToolResult> ExecuteAsync(
QueryMetricsInput input)
{
var query = input.MetricName switch
{
"request_rate" =>
$"rate(http_requests_total" +
$"{{service=\"{input.Service}\"}}[5m])",
"error_rate" =>
$"rate(http_requests_total" +
$"{{service=\"{input.Service}\",status=~\"5..\"}}[5m])",
"p99_latency" =>
$"histogram_quantile(0.99, " +
$"rate(http_request_duration_seconds_bucket" +
$"{{service=\"{input.Service}\"}}[5m]))",
_ => input.MetricName
};
var result = await _prometheus.QueryAsync(query);
return new McpToolResult
{
Content = new List<Content>
{
new TextContent
{
Text = $"Metric: {input.MetricName}\n" +
$"Service: {input.Service}\n" +
$"Value: {result.Value:F4}\n" +
$"Timestamp: {result.Timestamp}"
}
}
};
}
}
Extension Mechanisms Comparison
| Mechanism | Complexity | Flexibility | Maintenance |
|---|---|---|---|
| CLAUDE.md | Low | Medium | Low |
| Hooks | Low-Medium | Medium | Low |
| MCP Server | Medium-High | High | Medium |
| GitHub Actions | Medium | High | Medium |
22. Performance Optimization
Optimizing Claude Code's performance involves reducing latency, minimizing token usage, and maximizing the quality of each interaction. These optimizations have both cost and productivity implications.
Prompt Optimization
The most impactful optimization is writing better prompts. A well-structured prompt that provides file paths, function names, and expected outcomes can complete a task in 3-5 tool calls. A vague prompt might require 15-20 tool calls as the model explores the codebase to understand the context. Each tool call consumes tokens for both the tool call and the tool result, so reducing tool calls is the most effective way to reduce costs and latency.
Context Window Efficiency
Understanding how Claude Code uses the context window helps you make better decisions about what to include in your prompts. The model's output quality degrades as the context window fills up, so keeping the context clean and relevant improves results. Avoid pasting large amounts of code into your prompts — instead, let Claude Code read the files itself, which allows it to selectively load only the relevant portions.
// Performance metrics for Claude Code sessions
public class PerformanceMetrics
{
public int TotalApiCalls { get; set; }
public int TotalToolCalls { get; set; }
public int FileReads { get; set; }
public int FileEdits { get; set; }
public int BashCommands { get; set; }
public int SearchQueries { get; set; }
public TimeSpan TotalDuration { get; set; }
public decimal TotalCost { get; set; }
public int TotalInputTokens { get; set; }
public int TotalOutputTokens { get; set; }
public decimal CostPerToolCall =>
TotalToolCalls == 0 ? 0 :
TotalCost / TotalToolCalls;
public TimeSpan AverageApiLatency =>
TotalApiCalls == 0 ? TimeSpan.Zero :
TotalDuration / TotalApiCalls;
// Benchmark: typical values for a medium-complexity task
public static PerformanceMetrics MediumTask => new()
{
TotalApiCalls = 8,
TotalToolCalls = 15,
FileReads = 6,
FileEdits = 3,
BashCommands = 4,
SearchQueries = 2,
TotalDuration = TimeSpan.FromMinutes(2.5),
TotalCost = 0.22m,
TotalInputTokens = 35000,
TotalOutputTokens = 8000
};
}
Performance Tips
- Use Claude Sonnet for simple tasks — it is faster and cheaper
- Provide specific file paths in prompts to eliminate search tool calls
- Break complex tasks into smaller, focused tasks
- Use
--resumeto continue sessions instead of starting fresh - Keep CLAUDE.md concise — every token in CLAUDE.md is sent with every API call
- For batch operations, use
--printmode to avoid interactive overhead
23. Privacy and Data Handling
Privacy and data handling are critical considerations for any AI coding tool, especially in enterprise environments with sensitive codebases. Claude Code's privacy model is designed to give developers and organizations control over their data.
Data Flow
When you use Claude Code, the following data flows occur. Your source code is sent to the Anthropic API as part of the conversation context. The API processes the request and returns a response. Anthropic's data policies govern how this data is handled — API data is not used to train models by default. The code is transmitted over TLS-encrypted connections and is processed in Anthropic's infrastructure.
Privacy Controls
| Control | Description | Configuration |
|---|---|---|
| API data retention | API data is not used for training | Default for API users |
| Local execution | All tool execution happens locally | Always on |
| Permission controls | Granular control over what data is sent | settings.json |
| Deny patterns | Prevent access to sensitive files | settings.json deny list |
| No code storage | Anthropic does not store your code | API policy |
// Privacy-focused settings configuration
{
"permissions": {
"deny": [
"Read(**/.env*)",
"Read(**/secrets/**)",
"Read(**/*.pem)",
"Read(**/*.key)",
"Bash(*curl*api_key*)",
"Bash(*export*SECRET*)"
]
},
"privacy": {
"exclude_patterns": [
"**/.env*",
"**/secrets/**",
"**/*.pem",
"**/*.key",
"**/credentials.json"
]
}
}
The key principle is that Claude Code executes all tools locally on your machine. The only data that leaves your machine is the conversation context sent to the Anthropic API — which includes the code snippets, file contents, and tool results that are part of the current conversation. Tool execution, file operations, and shell commands all happen on your local machine.
24. The Future of AI Coding Agents
The trajectory of AI coding agents points toward increasingly autonomous and capable systems. Claude Code represents the current state of the art, but the field is evolving rapidly. Understanding these trends helps you prepare for the future of software development.
Trend 1: From Code Generation to System Design
Current AI coding agents excel at generating and modifying code within existing architectures. The next generation will be capable of system-level design — understanding requirements, proposing architectures, designing data models, and planning implementation strategies. Claude Code's extended thinking capability is an early manifestation of this trend.
Trend 2: Multi-Agent Collaboration
Future development environments will feature multiple AI agents working together — one agent handles the frontend, another the backend, a third writes tests, and a fourth performs code review. These agents will communicate through structured protocols, negotiate design decisions, and coordinate their work through shared context.
Trend 3: Continuous Learning
AI coding agents will learn from each interaction, adapting to your coding style, project conventions, and preferences over time. The CLAUDE.md file is a primitive form of this — manually maintained project knowledge. Future systems will automatically build and maintain this knowledge base.
Trend 4: Full Development Lifecycle
AI agents will eventually cover the entire software development lifecycle: requirements gathering, design, implementation, testing, deployment, monitoring, and incident response. Claude Code's GitHub Actions integration is an early step toward this vision, enabling automated code review and CI/CD workflows.
Trend 5: Domain-Specialized Agents
We will see specialized agents for different domains: database optimization agents, security audit agents, performance profiling agents, and accessibility compliance agents. These specialized agents will have deep knowledge of their domain and can provide expert-level assistance that general-purpose agents cannot match.
// Conceptual: multi-agent orchestration system
public class MultiAgentOrchestrator
{
private readonly Dictionary<string, ICodingAgent> _agents;
private readonly ITaskPlanner _planner;
public async Task<OrchestrationResult> ExecuteAsync(
string requirement)
{
// Step 1: Plan the task decomposition
var plan = await _planner.DecomposeAsync(requirement);
// plan.Tasks = [
// "Design API contracts",
// "Implement backend services",
// "Implement frontend components",
// "Write integration tests",
// "Perform security review"
// ]
var results = new List<TaskResult>();
foreach (var task in plan.Tasks)
{
// Step 2: Assign to specialized agent
var agent = SelectAgentForTask(task);
var context = await GatherContextAsync(
task, results);
// Step 3: Execute with context
var result = await agent.ExecuteAsync(
task, context);
results.Add(result);
// Step 4: Review and validate
if (result.RequiresReview)
{
var reviewer = _agents["reviewer"];
var review = await reviewer.ReviewAsync(
result, plan.Requirements);
if (review.HasIssues)
{
result = await agent.FixAsync(
result, review.Issues);
}
}
}
return new OrchestrationResult
{
AllTasksComplete = true,
Results = results
};
}
private ICodingAgent SelectAgentForTask(TaskItem task)
{
return task.Domain switch
{
"backend" => _agents["backend"],
"frontend" => _agents["frontend"],
"testing" => _agents["testing"],
"security" => _agents["security"],
_ => _agents["general"]
};
}
}
The pace of advancement in AI coding tools suggests that within the next two to three years, AI agents will handle the majority of routine coding tasks, freeing human developers to focus on system design, architectural decisions, and the creative aspects of software engineering that machines cannot replicate. The engineers who thrive in this new landscape will be those who understand how to effectively direct, verify, and integrate AI-generated code into production systems.
25. Interview Q&A
AI coding agents are an increasingly common topic in technical interviews, especially for senior and staff-level positions. These questions test your understanding of AI-assisted development, the tradeoffs of agentic systems, and your ability to evaluate and integrate new tools into team workflows.
Q1: How does Claude Code differ from a traditional code autocomplete tool?
A: The fundamental difference is autonomy. Traditional code assistants are reactive — they suggest completions based on the current line and surrounding context, and you accept or reject each suggestion. Claude Code is proactive — you give it a high-level goal, and it autonomously reads files, searches the codebase, writes code, runs tests, and iterates until the task is complete. This agentic loop of plan, act, observe, and reflect is qualitatively different from autocomplete. It means Claude Code can handle multi-file changes, debug failing tests, and perform tasks that require understanding the codebase as a whole rather than just the current file.
Q2: How does Claude Code manage the context window limitation when working with large codebases?
A: Claude Code uses several strategies. First, it does not load the entire codebase — it uses Glob and Grep to identify relevant files and reads only the portions it needs. Second, when the context fills up, it compresses older tool results into summaries, preserving semantic meaning while freeing tokens. Third, it uses incremental file reading with offset and limit to load only the relevant sections of large files. Fourth, it maintains a conversation structure that allows older turns to be summarized while recent context remains in full. The net effect is that Claude Code can work on codebases far larger than the context window by intelligently loading and discarding context as needed.
Q3: What are the security implications of giving an AI agent shell access?
A: Shell access is the most powerful and dangerous capability. Claude Code mitigates this through a tiered permission system: some commands are auto-approved (git status, dotnet build), some require session-level approval (file writes), and some require approval every time (potentially destructive commands). The deny list allows organizations to block specific patterns entirely. However, the risk is not zero — the model can be manipulated through prompt injection if untrusted content is included in the context. Best practices include running Claude Code in a sandboxed environment for untrusted tasks, maintaining strict deny lists, and never fully automating destructive operations.
Q4: How would you introduce Claude Code to a team that is skeptical about AI coding tools?
A: Start with a non-threatening use case like code review or documentation generation. Set up Claude Code as a GitHub Action that reviews pull requests and posts suggestions as comments. This lets the team see the tool's output without changing their workflow. Once the team sees value in automated reviews, introduce it for test generation — another low-risk use case that adds value. Finally, demonstrate it for actual coding tasks. Throughout this progression, emphasize that Claude Code is a tool that amplifies the developer's capabilities, not a replacement. Always maintain human review of all AI-generated code.
Q5: Compare the cost-effectiveness of Claude Code versus hiring additional developers.
A: Claude Code costs approximately $60-$165 per month per developer (using Sonnet for moderate usage). A junior developer costs significantly more in salary and benefits. However, this comparison is misleading because they serve different roles. Claude Code excels at repetitive tasks (boilerplate, test generation, refactoring, documentation) but cannot replace a developer's judgment about architecture, business requirements, or team dynamics. The most effective model is using Claude Code to amplify existing developers — a senior developer with Claude Code can accomplish the coding output of two to three developers for routine tasks while maintaining the architectural judgment that only experienced humans provide.
Q6: How does the Model Context Protocol (MCP) extend Claude Code's capabilities?
A: MCP is an open protocol that allows Claude Code to connect to external tool servers. An MCP server exposes tools (functions the model can call), resources (data the model can read), and prompts (pre-defined templates). When an MCP server is configured, its tools appear alongside Claude Code's built-in tools, and the model can call them the same way. This means you can give Claude Code access to your database, your monitoring system, your Jira board, your documentation wiki, or any other system by building an MCP server. It is a standardized extensibility mechanism that prevents vendor lock-in — the same MCP server works with Claude Code, Cursor, and any other MCP-compatible tool.
Q7: What role does CLAUDE.md play, and how does it affect the quality of Claude Code's output?
A: CLAUDE.md is loaded into the system prompt for every conversation, providing persistent project-specific instructions. It dramatically improves output quality because it gives Claude Code context about your project's architecture, conventions, build commands, and testing requirements. Without CLAUDE.md, Claude Code defaults to generic patterns that may not match your project. With a well-written CLAUDE.md, Claude Code's output is significantly more aligned with your team's standards. The key is keeping CLAUDE.md concise and actionable — every token in CLAUDE.md is sent with every API call, so it should contain high-value instructions, not verbose documentation.
Q8: How would you handle a situation where Claude Code generates code with a subtle security vulnerability?
A: This highlights the importance of human review. First, maintain a CLAUDE.md that explicitly states security requirements: input validation patterns, parameterized queries, proper authentication checks, and so on. Second, use the permission system to prevent Claude Code from executing code against production databases. Third, run automated security scanning tools (SAST, DAST) as part of the CI/CD pipeline to catch issues that both humans and AI might miss. Fourth, use Claude Code itself to review its own output for security issues — ask it to specifically check for OWASP Top 10 vulnerabilities. The key insight is that AI-generated code needs the same (or more) scrutiny as human-written code, not less.
Q9: What are the limitations of current AI coding agents like Claude Code?
A: Current limitations include: context window constraints that require careful management for large codebases; inability to truly understand business requirements (it implements what you say, not necessarily what you mean); risk of hallucinating APIs or patterns that do not exist; difficulty with highly creative or novel problem-solving; inability to maintain long-term project memory across sessions; and the need for human verification of all output. Additionally, the cost can be significant for heavy usage, the model can be influenced by prompt injection attacks, and the quality of output varies with the complexity and specificity of the task.
Q10: How do you see the role of software engineers evolving as AI coding agents become more capable?
A: The role will shift toward system design, architectural decisions, code review, and AI tool orchestration. Engineers will spend less time writing boilerplate code and more time defining what needs to be built and verifying that AI-generated code meets quality, security, and performance requirements. The most valuable engineers will be those who can effectively direct AI agents, design systems that are amenable to AI-assisted development, and maintain the judgment to know when AI output is correct and when it needs correction. Think of it as the transition from assembly language to high-level languages — the abstraction level rises, and the engineer's focus shifts to higher-level concerns.
Q11: Explain how you would set up Claude Code for a monorepo with multiple services.
A: In a monorepo, I would create directory-level CLAUDE.md files for each service. The root CLAUDE.md would contain the overall architecture, shared conventions, and build commands. Each service directory would have its own CLAUDE.md with service-specific details: its purpose, API contracts, database schema, and testing requirements. I would configure MCP servers for shared services like databases and monitoring. The permission settings would allow Claude Code to read across services but restrict writes to the currently relevant service. I would also set up a shared CLAUDE.md template that is automatically generated from the architecture documentation to keep it in sync.
Q12: How does extended thinking improve Claude Code's performance on complex tasks?
A: Extended thinking gives the model a separate reasoning stream where it can work through complex problems step by step before generating a solution. This is analogous to how an engineer sketches on a whiteboard before writing code. For tasks like debugging race conditions, designing database migrations, or planning large refactors, the model benefits from explicitly reasoning through the problem space, considering alternatives, and identifying potential issues before committing to an approach. The downside is additional cost and latency — the thinking tokens are output tokens that count toward your usage. For simple tasks, extended thinking adds unnecessary overhead.