← Claude Code & Certification

Claude Code in Action - Certification Study Guide

Claude Code in Action - Certification Study Guide

Module 1: What is Claude Code?

Key Notes

Claude Code is an AI-powered coding assistant that integrates seamlessly into the development workflow through a sophisticated tool-call architecture. It’s not just an autocomplete tool—it’s a full agent that can interact with your entire codebase, file system, and build system.

Architecture: The Tool Loop

Claude Code operates on a fundamental loop:

  1. User Prompt → Claude receives your request
  2. LLM Processing → Claude analyzes the request and decides what tools to use
  3. Tool Calls → Claude invokes tools (Read, Edit, Write, Bash, etc.) with specific parameters
  4. Tool Results → Tools execute and return their output/results
  5. LLM Synthesis → Claude analyzes results and makes next decision (more tools or respond)
  6. Response → Final answer to user, or loop continues if more investigation needed

This is NOT a sequential waterfall—it’s iterative. Claude may read files, run tests, edit code, run tests again, make refinements, all within a single conversation.

Core Capabilities

  • File System Access: Read files, write new files, edit existing files using structured patching
  • Bash Execution: Run shell commands for builds, tests, version control, package management
  • Codebase Navigation: Grep files, glob patterns, fast project scouting
  • Build Integration: Detect and run project build commands (npm, cargo, go, Python, Rust, Make)
  • Git Integration: Access git status, diffs, commits; can use gh CLI for GitHub operations
  • Context Management: Load and maintain relevant code context across long conversations
  • Tool Composition: Chain tools together to accomplish complex tasks (e.g., read → analyze → edit → build → test)

Why It Matters

Traditional LLMs are stateless text transformers. Claude Code adds statefulness and agency:

  • It remembers what it’s done (conversation history)
  • It can verify its own work (run tests after changes)
  • It can course-correct based on real feedback (error messages, test failures)
  • It operates on YOUR codebase, not generic examples

Best Practices

  1. Provide Context First: Before asking for changes, give Claude file paths, relevant code snippets, or error messages
  2. Be Specific: “Fix the bug in auth.ts” is vague. Better: “The login endpoint returns 401 even with correct credentials. See auth-handler.ts lines 45-67.”
  3. Use Read Before Edit: Let Claude read the actual file first rather than relying on memory or assumptions
  4. Ask for Verification: “Make sure the tests still pass after your changes” ensures Claude validates its work
  5. Chain Operations: Use multi-step conversations: read → understand → modify → test → review
  6. Watch the Loop: Pay attention to whether Claude is repeating edits to the same file (indicates a stuck loop)

Example

User: “Fix the JSON parsing error in my config loader”

Expected Claude flow:

1. Read the config-loader.ts file
2. Identify the JSON.parse() call and surrounding error handling
3. Run tests to see actual error message
4. Edit the error handling to properly catch/handle JSON errors
5. Run tests again to verify fix
6. Provide explanation of what was wrong and how it's fixed

Claude Code enables this entire workflow within one conversation without you having to run manual steps.


Module 2: Getting Hands On

Key Notes

Setup and Project Structure

Claude Code discovers and configures itself based on project files:

  1. CLAUDE.md — The primary configuration file. Can exist at three levels:

    • Project root (./CLAUDE.md) — Highest priority, project-specific overrides
    • .claude/ directory (.claude/CLAUDE.md) — Distribution/monorepo configuration
    • Nested subdirectories — Can use /init command to generate context in subdirectories
  2. Generate with /init command — Creates a CLAUDE.md file with:

    • Project type detection (Node, Python, Rust, Go, etc.)
    • Git repository info (remote, branch, root)
    • Package manager detection (npm, pip, cargo, etc.)
    • Framework detection (React, FastAPI, Rails, etc.)
    • Coding level and guidelines
    • Plan paths and documentation structure
  3. Context Compression with /compact — Token-efficient:

    • Summarizes conversation history
    • Removes duplicates
    • Maintains key decisions and outputs
    • Resets prompt overhead
    • Essential for long conversations

Adding Project Context

Three strategies for giving Claude Code access to your codebase:

Strategy 1: File References

  • Direct: ./src/auth.ts — Claude reads on demand
  • Pattern: src/**/*.test.ts — Can reference multiple files
  • Relative paths work automatically in the working directory

Strategy 2: Code Snippets in Messages

  • Paste relevant code directly in your prompt
  • Tells Claude “here’s what matters”
  • Saves Claude from having to read entire files
  • Use for error messages, specific functions, config sections

Strategy 3: Custom Commands

  • Create .claude/commands/ directory
  • Add markdown files with command definitions
  • Example: lint-check.md, run-tests.md, deploy.md
  • Can be invoked with /lint-check syntax
  • Useful for project-specific workflows

File Structure for Claude Code

Recommended project layout:

.
├── CLAUDE.md                 # Project context (generated/customized)
├── .claude/                  # Distribution config (submodule or symlink)
│   ├── settings.json         # Hook configuration
│   ├── commands/             # Custom commands
│   │   ├── run-tests.md
│   │   └── deploy.md
│   ├── hooks/                # Automated behaviors
│   ├── rules/                # Development workflow rules (auto-loaded)
│   └── docs/                 # Standards reference
├── docs/                     # Project documentation (read on-demand)
├── plans/                    # Implementation plans
│   ├── phase-01-setup.md
│   └── reports/              # Post-execution reports
└── src/                      # Source code

Best Practices

  1. Generate CLAUDE.md Early: Use /init to bootstrap context, customize as needed
  2. Keep CLAUDE.md Lean: Put detailed standards in docs/, not CLAUDE.md
  3. Use Relative Paths: Always use ./src/file.ts not /full/path/to/src/file.ts
  4. Reference Error Output: Paste actual error messages/test output—Claude learns from real signals
  5. Set Coding Level: Define your experience level in CLAUDE.md or settings for appropriate output depth
  6. Use Plans Directory: Organize multi-step work into plans/phase-XX-description.md files

Example

User creates a new project:

$ cd my-app
$ /init

Claude generates .claude/CLAUDE.md:

# my-app

**Project Type**: Node.js + React
**Package Manager**: npm
**Framework**: Next.js 14
**Git URL**: https://github.com/user/my-app
**Coding Level**: 5 (experienced)

## Key Directories
- `src/` — React components and pages
- `api/` — Next.js API routes
- `docs/` — Project documentation
- `plans/` — Implementation plans

## Build Command
npm run build

## Test Command
npm test

Now Claude Code automatically detects the tech stack and can:

  • Run npm test to check work
  • Parse test failures and fix code intelligently
  • Suggest Next.js-specific patterns

Module 3: Controlling Context

Key Notes

Context Management Strategies

Claude Code operates under context window limits. Strategic context management separates successful long-running projects from those that hit token ceilings.

Strategy 1: Use CLAUDE.md as the “Root of Truth”

  • Put all project context in CLAUDE.md (one read per session)
  • Reference specific files only when needed
  • Describe patterns and conventions, not every file
  • Keep CLAUDE.md under 2000 tokens

Strategy 2: Reference, Don’t Embed

  • Instead of pasting code: “See the error handler in utils/error.ts lines 45-67”
  • Claude reads on-demand, saves tokens if not needed
  • Error messages are cheap to paste (teach Claude what’s failing)

Strategy 3: Use /compact for Long Conversations

  • Summarizes conversation without losing information
  • Removes repetition and redundant context
  • Resets token usage, allows continuation in new context window
  • Safe to use multiple times in one project

Strategy 4: Subdirectory Support

  • Can work from subdirectories of monorepos
  • Generates plans and docs in current directory
  • Git root is detected but doesn’t force single context

Custom Commands

Custom commands are markdown files in .claude/commands/ that define reusable workflows.

Anatomy of a Command:

# Command Name
Description of what this command does.

## Trigger
/command-name

## Steps
1. Step one
2. Step two
3. Step three

## Example
Show example usage.

Common Commands:

  • /run-tests — Build, run tests, show summary
  • /typecheck — Type checking for TypeScript projects
  • /lint — Run linter and show violations
  • /deploy — Deploy to staging/production
  • /code-review — Self-review recent changes

These commands are invoked with / prefix and can reference files, run build steps, and generate reports.

MCP Server Integration

Model Context Protocol (MCP) servers extend Claude Code with external tool capabilities.

How It Works:

  1. Configure MCP server in settings.json
  2. MCP server exposes tools (e.g., “browse_web”, “query_database”)
  3. Claude Code treats MCP tools like built-in tools (Read, Edit, Bash)
  4. Can chain MCP tools with native tools in same conversation

Configuration in settings.json:

{
  "mcp": {
    "servers": [
      {
        "name": "browser",
        "command": "node",
        "args": ["mcp-browser-server.js"]
      },
      {
        "name": "postgres",
        "command": "python",
        "args": ["mcp-postgres.py"],
        "env": { "DATABASE_URL": "postgres://..." }
      }
    ]
  }
}

Common MCP Servers:

  • Browser automation — Selenium, Playwright, Puppeteer
  • Database query — SQL, MongoDB, REST APIs
  • Git operations — Extended git operations beyond bash
  • Cloud APIs — AWS, GCP, Azure SDKs
  • Document processing — PDF parsing, OCR

GitHub Integration

Claude Code can interact with GitHub via the gh CLI.

Key Operations:

gh pr create --title "Fix auth bug" --body "..."
gh pr review 42 --approve
gh issue view 123
gh api repos/owner/repo/issues/123/comments

Real-World Workflow:

  1. User: “Create a PR with the changes”
  2. Claude Code: Stages files, creates commit, runs gh pr create
  3. PR appears in GitHub with proper title and description
  4. Can also review PRs: gh pr review 42 --comment "LGTM"

Gotchas:

  • Requires GitHub CLI installed and authenticated (gh auth login)
  • Honors git branch names for PR titles
  • Can’t force-push or bypass branch protection rules

Best Practices

  1. Minimize CLAUDE.md: Link to docs, don’t embed everything
  2. Use Compact Early: Don’t wait until you hit token limits—compact at natural breakpoints
  3. Set Careful Scopes: Don’t load entire monorepos; focus on relevant subdirectories
  4. Cache External Tools: If using MCP servers, configure them once in settings.json
  5. Use gh for GitHub: Let Claude Code interface with GitHub programmatically
  6. Test Custom Commands Manually: Verify /my-command works before relying on it

Example

Long-running feature development:

  1. Start: Read feature spec in CLAUDE.md, reference test files
  2. Mid-Session: Make multiple edits, run tests after each
  3. Hit Token Limit: Use /compact to summarize progress
  4. Continue: Refresh context with new investigation as needed
  5. Complete: Create PR with gh pr create, link to issue

Module 4: Hooks and the SDK

Key Notes

Hook System Architecture

Hooks are automated behaviors triggered by Claude Code events. They run in response to specific actions (tool use, session start, etc.) and can:

  • Block operations (with user approval flow)
  • Inject additional context
  • Run security/compliance checks
  • Integrate external systems
  • Provide feedback loops

Hook Execution Model:

  • Synchronous: Hooks run during tool execution, block until complete
  • Timeout Protected: Default 30s, configurable per hook
  • Fail-Open: If a hook crashes, the operation is allowed (safety first)
  • JSON I/O: Hooks receive JSON on stdin, output JSON on stdout
  • Exit Codes: 0=allow, 2=block, other codes are treated as allow

Hook Lifecycle Events

Hooks attach to seven lifecycle events:

1. SessionStart

  • Fires once when session starts (startup, resume, clear, compact)
  • Purpose: Load configuration, detect project info, initialize environment
  • Real Examplesession-init.cjs:
    // Detect project type, package manager, framework
    const detections = {
      type: detectProjectType(config.project?.type),
      pm: detectPackageManager(config.project?.packageManager),
      framework: detectFramework(config.project?.framework)
    };
    
    // Persist to environment for other hooks to use
    writeEnv(envFile, 'CK_PROJECT_TYPE', detections.type || '');
    writeEnv(envFile, 'CK_PACKAGE_MANAGER', detections.pm || '');
    

2. UserPromptSubmit

  • Fires when user submits a prompt (after compose, before LLM processing)
  • Purpose: Validate input, inject reminders, check token usage
  • Example Use Cases: Token efficiency warnings, dev rules reminders, usage context awareness

3. PreToolUse

  • Fires BEFORE each tool is invoked
  • Purpose: Validate arguments, block risky operations, inject context
  • Real Examples:
    • Loop Detection: Track file edits, warn if same file edited 5+ times
    • Privacy Block: Check for sensitive files (.env, secrets), require approval
    • Scout Block: Block access to node_modules, dist/ directories
    • Package Guard: Require version pinning on npm install commands
    • Build Sensor: Flag uncompilable code

4. PostToolUse

  • Fires AFTER tool execution completes successfully
  • Purpose: Feedback loops, security audits, context injection
  • Real Examplebuild-sensor.cjs:
    // Auto-run build after code edits
    const buildCmdRaw = detectBuildCommand();  // Find npm run build, cargo check, etc.
    try {
      execSync(cmd, { timeout: 30000 });
      // Success — swallow output (zero token cost)
    } catch (buildError) {
      // Failure — surface error to Claude
      console.log(`🔴 BUILD FAILED: ${buildError.message}`);
    }
    
    This creates a tight feedback loop: Edit → Auto-build → See errors immediately

5. Stop

  • Fires when user clicks Stop button
  • Purpose: Cleanup, verify uncommitted changes, ask confirmation
  • Example: Prompt user “You have unsaved changes. Continue stopping?”

6. SessionEnd

  • Fires when session ends
  • Purpose: Cleanup, save state, archive logs
  • Example: Compress plan artifacts, archive hook logs

7. SubagentStart

  • Fires when a subagent is spawned
  • Purpose: Initialize subagent context, pass parent state
  • Example: Set CLAUDE_CODE_TASK_LIST_ID for multi-agent task coordination

Hook Configuration in settings.json

Hooks are wired in settings.json under the hooks key:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/loop-detection.cjs",
            "timeout": 10
          }
        ]
      },
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/package-guard.cjs",
            "timeout": 10
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/build-sensor.cjs",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Matcher Patterns:

  • "*" — Match all tools
  • "Tool1|Tool2|Tool3" — Match multiple tools (pipe-separated)
  • "Bash" — Match single tool
  • Patterns are regex, evaluated left-to-right

Hook Input/Output Format

Input (stdin JSON):

{
  "hook_event_name": "PreToolUse",
  "tool_name": "Edit",
  "tool_input": {
    "file_path": "/path/to/file.ts",
    "old_string": "...",
    "new_string": "..."
  },
  "session_id": "abc-123",
  "source": "startup"
}

Output (stdout JSON) — For blocking hooks:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "block",  // "allow" or "block"
    "permissionReason": "Sensitive file access requires approval"
  }
}

Output — For context injection:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "additionalContext": "⚠️ LOOP DETECTED: You've edited this file 5 times this session..."
  }
}

Real Hook Examples

Example 1: Loop Detection (PreToolUse) From loop-detection.cjs:

const THRESHOLD = 5;

function main() {
  const stdin = fs.readFileSync(0, 'utf8').trim();
  const hookData = JSON.parse(stdin);
  const filePath = hookData.tool_input?.file_path || '';
  
  // Track edits per file in temp state
  const trackFile = path.join(os.tmpdir(), `ck-loop-${sessionId}.json`);
  let tracking = {};
  if (fs.existsSync(trackFile)) {
    tracking = JSON.parse(fs.readFileSync(trackFile, 'utf8'));
  }
  
  tracking[key] = (tracking[key] || 0) + 1;
  fs.writeFileSync(trackFile, JSON.stringify(tracking));
  
  const count = tracking[key];
  if (count >= THRESHOLD) {
    const warning = `⚠️ LOOP DETECTED: ${count} edits to ${path.basename(filePath)}.
Stop. Re-read the actual error. Try a different approach.`;
    
    console.log(JSON.stringify({
      hookSpecificOutput: {
        hookEventName: "PreToolUse",
        permissionDecision: "allow",  // Still allow, just warn
        additionalContext: warning
      }
    }));
  }
  process.exit(0);
}

How It Works:

  1. Reads file path from tool input
  2. Maintains count in /tmp/ck-loop-{sessionId}.json
  3. When count >= 5, outputs warning as additionalContext
  4. Claude sees the warning and should investigate root cause
  5. Exit code 0 = allow the operation (hooks don’t block, just warn)

Example 2: Privacy Block (PreToolUse) From privacy-block.cjs:

async function main() {
  let input = '';
  for await (const chunk of process.stdin) {
    input += chunk;
  }
  
  const hookData = JSON.parse(input);
  const { tool_input: toolInput, tool_name: toolName } = hookData;
  
  // Check if accessing sensitive files (.env, .aws/credentials, etc.)
  const result = checkPrivacy({
    toolName,
    toolInput,
    options: { allowBash: true }
  });
  
  if (result.approved) {
    // User previously approved with APPROVED: prefix
    console.error(formatApprovalNotice(result.filePath));
    process.exit(0);
  }
  
  if (result.blocked) {
    // No approval - block and ask for user permission
    console.error(formatBlockMessage(result.filePath));
    process.exit(2);  // Exit 2 = BLOCK
  }
  
  process.exit(0);
}

How It Works:

  1. Check if file is in sensitive list (.env, .aws, .git/config, etc.)
  2. Check if user prefixed with APPROVED:.env (approval token)
  3. If blocked, output block message with AskUserQuestion JSON
  4. Exit 2 = block the Read/Edit
  5. User approves via dialog → Claude retries with APPROVED:.env prefix
  6. Hook sees approval prefix, allows access

Example 3: Package Guard (PreToolUse) From package-guard.cjs:

function detectInstall(command) {
  const INSTALL_PATTERNS = [
    { regex: /\b(npm|pnpm|yarn)\s+(install|add|i)\s+/, versionPin: /@[\d^~]/, ecosystem: 'node' },
    { regex: /\b(pip|uv)\s+install\s+/, versionPin: /[=<>!]=/, ecosystem: 'python' },
    { regex: /\bcargo\s+add\s+/, versionPin: /@[\d]/, ecosystem: 'rust' },
  ];
  
  for (const p of INSTALL_PATTERNS) {
    if (p.regex.test(command)) {
      const afterCmd = command.replace(p.regex, '');
      const packages = afterCmd.split(/\s+/).filter(a => a && !a.startsWith('-'));
      return { ...p, packages };
    }
  }
  return null;
}

function checkVersionPinned(packages, versionPin) {
  const unpinned = packages.filter(pkg => !versionPin.test(pkg));
  return unpinned;
}

if (event === 'PreToolUse') {
  const match = detectInstall(command);
  if (!match) process.exit(0);
  
  const unpinned = checkVersionPinned(match.packages, match.versionPin);
  if (unpinned.length > 0) {
    const examples = {
      node: 'npm install lodash@4.17.21',
      python: 'pip install requests==2.31.0',
      rust: 'cargo add serde@1.0.193'
    };
    
    process.stderr.write(
      `Version pin required. Unpinned: ${unpinned.join(', ')}\n` +
      `Example: ${examples[match.ecosystem]}`
    );
    process.exit(2);  // BLOCK
  }
}

How It Works:

  1. Detect npm install lodash (unversioned)
  2. Extract package names: ['lodash']
  3. Check if version is pinned: /@[\d^~]/ (@ followed by digit/caret/tilde)
  4. If unpinned, show error and exit 2 (block)
  5. Claude retries with npm install lodash@4.17.21 (pinned)
  6. Hook validates version pin, exits 0 (allow)

Example 4: Build Sensor (PostToolUse) From build-sensor.cjs:

function detectBuildCommand() {
  const cwd = process.cwd();
  
  // Check for npm/TypeScript
  if (fs.existsSync(path.join(cwd, 'package.json'))) {
    const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
    if (pkg.scripts?.typecheck) return 'npm run typecheck';
    if (pkg.scripts?.build) return 'npm run build';
    if (fs.existsSync(path.join(cwd, 'tsconfig.json'))) return 'npx tsc --noEmit';
  }
  
  // Check for Python
  if (fs.existsSync(path.join(cwd, 'pyproject.toml'))) {
    return 'python3 -m py_compile';
  }
  
  // Check for Rust
  if (fs.existsSync(path.join(cwd, 'Cargo.toml'))) {
    return 'cargo check';
  }
  
  return null;
}

const buildCmdRaw = detectBuildCommand();
if (!buildCmdRaw) process.exit(0);

try {
  execSync(cmd, {
    shell: true,
    encoding: 'utf8',
    timeout: 30000,
    stdio: ['pipe', 'pipe', 'pipe'],
    cwd: process.cwd()
  });
  // Success — swallow output (zero context cost)
} catch (buildError) {
  // Failure — surface error
  const stderr = (buildError.stderr || '').trim();
  const output = stderr.split('\n').slice(-40).join('\n');
  
  if (output) {
    console.log(`🔴 BUILD FAILED after editing ${path.basename(filePath)}:\n${output}`);
  }
}

How It Works:

  1. Auto-detect build command from project (npm, cargo, python, etc.)
  2. After Edit/Write, run the build command
  3. If succeeds, output nothing (save tokens)
  4. If fails, print the last 40 lines of error output
  5. Claude sees build error and can fix it immediately
  6. This creates tight feedback: Edit → Instant validation

Hook Gotchas and Patterns

Gotcha 1: Async/Await Confusion Hooks must exit with process.exit(). Don’t rely on implicit return:

// ❌ WRONG: async function without exit
async function main() {
  await somePromise();
  // Function returns, but hook still running = timeout
}

// ✅ CORRECT: explicit exit
async function main() {
  await somePromise();
  process.exit(0);  // Force exit
}

Gotcha 2: Timeout Limits Hooks default to 30s timeout. Long operations (build, tests) need higher timeout:

{
  "type": "command",
  "command": "node build-heavy-hook.cjs",
  "timeout": 120  // 2 minutes for heavy builds
}

Gotcha 3: Fail-Open by Default Hooks that crash (unhandled exceptions) exit with non-zero code, which is treated as “allow”. This is intentional—better to allow than block on hook failure.

// Always include crash wrapper
try {
  const mainResult = runHook();
  process.exit(0);
} catch (e) {
  // Log but don't crash — exit 0 to fail-open
  console.error(`Hook crashed: ${e.message}`);
  process.exit(0);  // Fail-open: allow operation
}

Gotcha 4: JSON Parsing Fragility Invalid JSON in stdin → hook crashes → fail-open. Handle gracefully:

let data;
try {
  data = JSON.parse(stdin);
} catch (e) {
  console.error('Invalid JSON, allowing operation');
  process.exit(0);  // Fail-open
}

Gotcha 5: File System Race Conditions Multiple hooks may run in parallel. Don’t assume file state:

// ❌ WRONG: Check then use
if (fs.existsSync(file)) {
  fs.readFileSync(file);  // File might be deleted by another hook
}

// ✅ CORRECT: Try/catch
try {
  const content = fs.readFileSync(file, 'utf8');
} catch (e) {
  // File doesn't exist, handle gracefully
  process.exit(0);
}

Pattern 1: Decision Injection Hooks output JSON to make decisions visible to Claude:

console.log(JSON.stringify({
  hookSpecificOutput: {
    hookEventName: "PreToolUse",
    permissionDecision: "allow",
    additionalContext: "⚠️ This operation is expensive. Verify you meant it."
  }
}));

Pattern 2: Environment Persistence SessionStart hook sets env vars that other hooks read:

// SessionStart
writeEnv(envFile, 'CK_PROJECT_TYPE', 'node');
writeEnv(envFile, 'CK_PACKAGE_MANAGER', 'npm');

// Other hooks
const projectType = process.env.CK_PROJECT_TYPE;  // Already available

Pattern 3: Shared Libraries Real distribution uses hooks/lib/ for shared functions:

// session-init.cjs
const { detectProjectType, detectPackageManager } = require('./lib/project-detector.cjs');

// privacy-block.cjs
const { checkPrivacy, isSensitiveFile } = require('./lib/privacy-checker.cjs');

Claude Code SDK

The Claude Code SDK allows programmatic interaction with Claude from Node.js/TypeScript.

Installation:

npm install @anthropic-ai/sdk

Basic Usage:

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const response = await client.messages.create({
  model: "claude-opus-4-1",
  max_tokens: 2048,
  tools: [
    {
      name: "read_file",
      description: "Read a file from disk",
      input_schema: {
        type: "object",
        properties: {
          path: { type: "string" }
        },
        required: ["path"]
      }
    }
  ],
  messages: [
    {
      role: "user",
      content: "What does auth.ts do?"
    }
  ]
});

Tool Loop in SDK:

let response = await client.messages.create({
  model: "claude-opus-4-1",
  max_tokens: 4096,
  tools: [/* tool definitions */],
  messages: [{ role: "user", content: "Fix the auth bug" }]
});

while (response.stop_reason === "tool_use") {
  const toolUse = response.content.find(b => b.type === "tool_use");
  
  // Execute the tool
  const result = await executeTool(toolUse.name, toolUse.input);
  
  // Continue conversation with result
  response = await client.messages.create({
    model: "claude-opus-4-1",
    max_tokens: 4096,
    tools: [/* same tools */],
    messages: [
      /* previous messages */
      { role: "assistant", content: response.content },
      {
        role: "user",
        content: [
          {
            type: "tool_result",
            tool_use_id: toolUse.id,
            content: JSON.stringify(result)
          }
        ]
      }
    ]
  });
}

Running Claude as Subprocess:

# From bash or Node.js
claude code --project ./my-app "Fix the login endpoint"

Key SDK Features:

  • Prompt Caching: Cache system prompts and context for cost savings
  • Batch Processing: Submit multiple requests, retrieve results asynchronously
  • Streaming: Real-time token streaming for long responses
  • Vision: Send images, PDFs for analysis
  • Extended Thinking: Enable Claude’s internal reasoning for complex problems

Best Practices

  1. Hooks are About Guardrails, Not Logic: Use for validation, not for implementing features
  2. Fail-Open Always: If a hook crashes, the operation should be allowed
  3. Keep Hooks Fast: 30s timeout is strict. Optimize or increase timeout
  4. Test Hooks Independently: Verify JSON input/output locally before deploying
  5. Use Shared Libraries: Extract common logic to hooks/lib/ for reuse
  6. Document the Loop: For blocking hooks, explain the approval flow (e.g., privacy-block’s AskUserQuestion pattern)
  7. Monitor Hook Performance: Log slow hooks, migrate heavy logic to SessionStart
  8. SDK for Programmatic Use: Use SDK when you need to run Claude Code as part of automation/CI

Example

Complete hook workflow for secure code review:

File: .claude/hooks/code-review-gate.cjs

const fs = require('fs');

// PreToolUse: Code Review Gate
// Blocks direct pushes; requires code review first

try {
  const hookData = JSON.parse(fs.readFileSync(0, 'utf8'));
  const { tool_name: toolName, tool_input: toolInput } = hookData;
  
  // Only apply to Bash git commands
  if (toolName !== 'Bash' || !toolInput.command?.includes('git push')) {
    process.exit(0);
  }
  
  // Check if there are pending PRs (use gh CLI)
  const { execSync } = require('child_process');
  try {
    const prs = execSync('gh pr list --state open --json number', {
      encoding: 'utf8'
    });
    
    if (!prs || prs.trim() === '[]') {
      console.error(
        '❌ CODE REVIEW GATE: No open PR found.\n' +
        'Push requires an open code review. Use: gh pr create'
      );
      process.exit(2);  // BLOCK
    }
  } catch (e) {
    // GitHub not available, allow
    process.exit(0);
  }
  
  process.exit(0);  // ALLOW if PR exists
} catch (e) {
  process.exit(0);  // Fail-open
}

Configuration: settings.json

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/code-review-gate.cjs",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

Flow:

  1. Claude tries: git push origin main
  2. Hook triggers, checks for open PRs
  3. No PRs found → Hook exits 2 (block)
  4. Error message shown to Claude
  5. Claude creates PR: gh pr create --title "..." --body "..."
  6. Retries git push → Hook sees open PR → Allows push

Module 5: Wrapping Up

Key Takeaways

The Three Pillars of Claude Code

  1. Tool Integration — Claude Code is an agent with access to your codebase, file system, and tools
  2. Context Management — Strategic use of CLAUDE.md, file references, and /compact keeps tokens efficient
  3. Automation via Hooks — PreToolUse/PostToolUse hooks create feedback loops and guardrails

Real-World Patterns

Pattern 1: Tight Feedback Loop (Build Sensor)

  • Edit code → Auto-build → See errors immediately → Fix and retry
  • Eliminates manual test cycles
  • Context-efficient (swallows success output)

Pattern 2: Approval Flow (Privacy Block)

  • Attempt sensitive operation → Blocked → User approves → Retry with approval token → Allowed
  • Enables secure workflows (secrets management)
  • UX-friendly (no token waste on repeated blocks)

Pattern 3: Multi-Agent Coordination

  • Parent agent sets CLAUDE_CODE_TASK_LIST_ID
  • Subagents inherit task list context
  • All agents contribute to shared plan
  • Enables parallel feature development

Pattern 4: Context Compression

  • Long conversations → Hit token limit → /compact → Continue
  • Summarization preserves key decisions
  • Reset overhead for fresh context window

Integration Checklist

Before deploying Claude Code in a project:

  • Generate CLAUDE.md with /init
  • Verify build commands detected (npm, cargo, python, etc.)
  • Test file access with relative paths
  • Configure hooks for your workflow (build-sensor, privacy-block, etc.)
  • Set up GitHub integration if using PRs
  • Define custom commands in .claude/commands/
  • Document project-specific context in CLAUDE.md
  • Test /compact workflow for long sessions
  • Set coding level for appropriate output depth

Security Considerations

Privacy:

  • Use privacy-block hook to protect .env, .aws, credentials
  • Require approval prefix for sensitive file access
  • Never commit API keys to git (use environment variables)

Package Security:

  • Use package-guard hook to enforce version pinning
  • Run security audits after new dependencies
  • Regularly update dependencies

Code Review:

  • Implement code review gates (custom hooks)
  • Use gh pr to create reviewed PRs
  • Don’t auto-merge without human approval

Git Safety:

  • Hooks prevent dangerous operations (force push, git reset –hard)
  • Require branch protection rules on main
  • Enable commit signing

Common Pitfalls

  1. Embedding All Context in CLAUDE.md → Bloats token usage

    • Solution: Use CLAUDE.md as map, detailed standards in docs/
  2. Not Using /compact → Hits token limits mid-project

    • Solution: Compact at natural breakpoints (feature complete, phase transition)
  3. Hooks with No Exit Code → Causes timeouts

    • Solution: Always end hooks with process.exit(0) or process.exit(2)
  4. Ignoring Loop Warnings → Infinite fix/fail cycles

    • Solution: When loop-detection fires, pause and reconsider approach
  5. Missing MCP Server Setup → Can’t use external tools

    • Solution: Configure MCP servers in settings.json before needing them

Assessment Preparation

Key Concepts:

  1. Tool loop architecture (User → LLM → Tool → Result → LLM)
  2. Three levels of CLAUDE.md (project root, .claude/, subdirectories)
  3. Hook lifecycle events and their purposes (SessionStart, PreToolUse, PostToolUse, etc.)
  4. Hook exit codes (0=allow, 2=block)
  5. JSON input/output format for hooks
  6. Real patterns: loop detection, privacy blocking, build sensing, package guarding
  7. Custom commands for project workflows
  8. MCP server integration for external tools
  9. GitHub integration via gh CLI
  10. SDK for programmatic Claude Code usage

Practice Scenarios:

  • How would you prevent accidental npm install of unversioned packages?
  • Design a hook that warns Claude after 3 failed test runs in a row
  • Explain the approval flow for accessing .env files
  • How does build-sensor provide feedback without token overhead?
  • What happens if a hook crashes? (Fail-open = allow)
  • How would you coordinate work across multiple subagents?

Resources

Example: Full Integration Flow

Step 1: Initialize

cd my-react-app
/init
# Generates CLAUDE.md with React/TypeScript context

Step 2: Configure Hooks

// .claude/settings.json
{
  "hooks": {
    "PreToolUse": [
      { "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "node ... /hooks/loop-detection.cjs" }] },
      { "matcher": "Bash|Read", "hooks": [{ "type": "command", "command": "node ... /hooks/privacy-block.cjs" }] }
    ],
    "PostToolUse": [
      { "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "node ... /hooks/build-sensor.cjs", "timeout": 30 }] }
    ]
  }
}

Step 3: Use Claude Code

User: "Fix the auth bug in login page"

Claude:
1. Reads CLAUDE.md → React + TypeScript context
2. Attempts Edit on pages/login.tsx
3. PreToolUse hook checks for loop (count=1, OK)
4. Edit succeeds
5. PostToolUse hook runs build: "npm run typecheck"
6. Fails → Claude sees error output
7. Analyzes error, makes next edit
8. Repeats until tests pass

User can /compact if conversation gets long.

Step 4: Create PR and Push

Claude: "I'll create a PR for review"
1. Stage changes: git add pages/login.tsx
2. Commit: git commit -m "fix: auth bug in login page"
3. Create PR: gh pr create --title "Fix auth bug" --body "..."
4. Git push origin feature-branch
   - PreToolUse hook checks: Is there an open PR? Yes → Allow

Summary

Claude Code transforms development by making AI a collaborative coding partner with:

  • Architecture: Agent + tools + tool loop + context management
  • Control: CLAUDE.md for project context, hooks for guardrails, custom commands for workflows
  • Safety: Privacy blocks, approval flows, build feedback, package validation
  • Scalability: /compact for long sessions, MCP servers for external tools, SDK for automation

The hook system is the differentiator—it enables tight feedback loops (build-sensor), security gates (privacy-block, package-guard), and approval workflows (loop-detection, code-review-gate) without token waste.

Master these concepts and you’ll build highly productive, safe AI-assisted development workflows.