system-design54 min read

Claude Code: The Complete Guide to Anthropic's CLI Coding Agent — A Senior+ Guide | Ayodhyya

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

Senior+ Guide 60+ min read 10,000+ words Ayodhyya

Table of Contents

  1. Introduction — The Rise of Agentic Coding
  2. What Is Claude Code?
  3. Architecture and How It Works
  4. Installation and Setup
  5. Core Features — File Editing, Bash Execution, and Search
  6. Multi-Turn Agentic Workflow
  7. Tool Use and Function Calling
  8. Context Window Management
  9. System Prompt Engineering
  10. Custom Instructions with CLAUDE.md
  11. Permissions and Security Model
  12. MCP — Model Context Protocol Integration
  13. GitHub Actions and CI/CD Integration
  14. Hooks and Automation
  15. Git Workflow Integration
  16. Comparing Claude Code vs Cursor vs GitHub Copilot
  17. Pricing and Token Usage
  18. Best Practices and Tips
  19. Common Pitfalls
  20. Real-World Use Cases
  21. Extending Claude Code with Plugins
  22. Performance Optimization
  23. Privacy and Data Handling
  24. The Future of AI Coding Agents
  25. 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.

Who This Guide Is For: Senior+ software engineers, tech leads, architects, and engineering managers who want to understand Claude Code at a deep technical level and integrate it into their teams and workflows effectively.

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

FeatureTraditional AssistantsClaude Code
Interaction ModelAutocomplete / chatAgentic task execution
File AccessOpen editor tab onlyFull filesystem
Shell AccessNone or limitedFull shell execution
Multi-File EditsManual, one file at a timeAutomatic across codebase
Test ExecutionNot possibleRuns tests, reads results, iterates
Git IntegrationBasic (commit messages)Branching, commits, PRs, code review
ExtensibilityPlugin APIsMCP servers + hooks + CLAUDE.md
Context WindowSingle file or small selectionFull codebase with smart compression
Running EnvironmentInside IDETerminal (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.

graph TB subgraph "Claude Code Runtime" A[User Input] --> B[System Prompt Builder] B --> C[Claude API Call] C --> D{Response Type} D -->|Text| E[Display to User] D -->|Tool Calls| F[Tool Executor] F --> G[Tool Results] G --> C E --> H{Task Complete?} H -->|No| C H -->|Yes| I[Done] end subgraph "Available Tools" J[Read File] K[Write File] L[Edit File] M[Bash Command] N[Glob Search] O[Grep Search] P[MCP Tools] end F --> J F --> K F --> L F --> M F --> N F --> O F --> P

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

FileLocationPurpose
Project CLAUDE.md./CLAUDE.mdProject-specific instructions, committed to repo
User CLAUDE.md~/.claude/CLAUDE.mdUser-level instructions, applies to all projects
Settings~/.claude/settings.jsonGlobal settings (model, permissions, theme)
Project Settings.claude/settings.jsonProject-level settings, committed to repo
Local Settings.claude/settings.local.jsonProject-level local overrides, gitignored
MCP Config~/.claude/mcp.jsonMCP 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

ToolPrimary UseRisk LevelDefault Permission
ReadRead file contentsLowAlways allowed
WriteCreate or overwrite filesMediumAsk on first use
EditSurgical string replacement in filesMediumAsk on first use
BashExecute shell commandsHighAsk on first use
GlobFind files by patternLowAlways allowed
GrepSearch file contents with regexLowAlways 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.

sequenceDiagram participant U as User participant C as Claude Code participant T as Tools participant S as Claude API U->>C: "Add rate limiting to the API" C->>S: Analyze request + build context S-->>C: Plan: read controllers, identify endpoints C->>T: Glob for controller files T-->>C: List of controller files C->>T: Read each controller T-->>C: Controller source code C->>S: Analyze code + design rate limiter S-->>C: Generate rate limiter class C->>T: Write RateLimitMiddleware.cs T-->>C: Success C->>T: Edit Program.cs to register middleware T-->>C: Success C->>T: Run dotnet build T-->>C: Build succeeded C->>T: Run dotnet test T-->>C: 3 tests failed C->>S: Analyze test failures S-->>C: Fix needed in test setup C->>T: Edit test file T-->>C: Success C->>T: Run dotnet test T-->>C: All tests pass C->>U: "Done. Added rate limiting middleware..."

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

StrategyWhen UsedImpact
Incremental file readingLarge filesRead only relevant lines
Tool result compressionContext approaching limitSummarize older tool outputs
Conversation summarizationLong conversationsCondense older turns into summaries
Selective search scopingLarge codebasesNarrow searches by file type, directory
File path referencesAfter initial readReference 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
Pro Tip: After Claude Code completes a task, ask it to update the CLAUDE.md with any new conventions or patterns it discovered during the task. This creates a feedback loop that progressively improves the quality of Claude Code's output over time.

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

TierBehaviorToolsConfiguration
Auto-allowExecute without askingRead, Glob, GrepAlways on
Session-allowAsk first time, then auto-approve for sessionWrite, EditConfigurable
Ask-every-timeAlways ask for confirmationBash (non-destructive)Default for bash
DenyNever executeUser-defined patternsConfigurable

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.

Warning: Never configure Claude Code with fully auto-approve permissions on a production system. Even with the best CLAUDE.md instructions, a model can make unexpected tool calls. The permission system exists for a reason — use it to maintain a human-in-the-loop for all destructive operations.

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.

graph LR A[Claude Code] -->|MCP Protocol| B[MCP Server 1] A -->|MCP Protocol| C[MCP Server 2] A -->|MCP Protocol| D[MCP Server 3] B --> E[Database] C --> F[Internal API] D --> G[Documentation System]

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 ServerPurposeKey Capabilities
Postgres MCPDatabase accessQuery, schema inspection, table management
GitHub MCPGitHub integrationIssues, PRs, repos, code search
Filesystem MCPExtended file accessSandboxed file operations
Puppeteer MCPBrowser automationScreenshots, page interaction, scraping
Slack MCPTeam communicationSend messages, read channels
Docker MCPContainer managementList, 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

sequenceDiagram participant D as Developer participant GH as GitHub participant A as GitHub Actions participant CC as Claude Code participant C as Claude API D->>GH: Open Pull Request GH->>A: Trigger workflow A->>CC: Launch with PR context CC->>C: Analyze PR diff C-->>CC: Review plan CC->>CC: Read changed files CC->>CC: Read related tests CC->>CC: Check conventions in CLAUDE.md CC->>C: Generate review C-->>CC: Review feedback CC->>GH: Post review comments GH->>D: Review notification
// 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 CaseTriggerClaude Code Action
Code ReviewPull request openedReview diff, post comments, approve/request changes
Auto-fix Lint ErrorsPull request openedRead lint errors, fix code, push commit
Test GenerationPull request mergedAnalyze changes, generate missing tests, open PR
Documentation UpdatePush to mainRead code changes, update relevant docs
Issue TriageIssue openedAnalyze issue, label it, suggest assignee
Dependency AuditScheduled (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 TypeFires WhenUse Case
PreToolCallBefore any tool executesValidation, logging, blocking
PostToolCallAfter any tool executesFormatting, linting, notifications
PreToolCall:WriteBefore file writeLicense header injection
PostToolCall:EditAfter file editAuto-format with dotnet format
PostToolCall:BashAfter bash commandParse 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
        };
    }
}
Interview Context: Hooks demonstrate the extensibility of agentic systems. Understanding how hooks work shows that you can design systems that are both autonomous and controllable — the hook mechanism provides interception points where human-defined rules can influence agent behavior without modifying the agent itself.

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.