Skip to content

Instantly share code, notes, and snippets.

@lagergren
Created February 2, 2026 08:43
Show Gist options
  • Select an option

  • Save lagergren/f3d022edca93d772ec65a79ddb3e1ec0 to your computer and use it in GitHub Desktop.

Select an option

Save lagergren/f3d022edca93d772ec65a79ddb3e1ec0 to your computer and use it in GitHub Desktop.
AI Coding with Claude Code: A Practical Guide

This section contains various documents and resources for AI based coding.

AI Coding with Claude Code: A Practical Guide

This guide covers how to get the most out of Claude Code for software engineering. Whether you're a seasoned developer or just starting out, these patterns will help you work more effectively.


Table of Contents

  1. Think First
  2. Architecture and Planning
  3. CLAUDE.md Configuration
  4. Understanding Context Windows
  5. Prompting Techniques
  6. Model Selection
  7. Skills and Slash Commands
  8. Subagents
  9. Plugins
  10. MCP (Model Context Protocol)
  11. Hooks
  12. When Claude Gets Stuck
  13. Building Automated Systems
  14. Summary

Think First

Most people assume that with Claude Code and other AI tools, the first thing you need to do is type (or start talking). But that's probably one of the biggest mistakes that you can make straight off the bat. The first thing that you actually need to do is think.

10 out of 10 times, the output I've gotten with plan mode did significantly better than when I just started talking and spewing everything into Claude Code. It's not even close.

Now for some of you, this is easier said than done. You might not have years of software engineering experience that would allow you to think about this on your own. To this extent I have two pieces of advice:

  1. Start learning. You are handicapping yourself if you never pick up on this, even if just a little bit at a time.
  2. Have a deep back and forth with ChatGPT/Gemini/Claude, where you describe exactly what you want to build, you ask the LLM for the various options you can take in terms of system design, and ultimately the two of you settle on a solution. You and the LLM should be asking each other questions, not just a one way street.

This applies to everything. This also includes very small tasks like summarizing emails. Before you ask Claude to build a feature, think about the architecture. Before you ask it to refactor something, think about what the end state should look like. Before you ask it to debug, think about what you actually know about the problem. The more information that you have in plan mode, the better your output is actually going to be because the better the input is going to be.

The pattern is consistent: thinking first, then typing, produces dramatically better results than typing first and hoping Claude figures it out.


Architecture and Planning

Architecture, especially in software engineering, is a little bit like giving a person the output and nothing more. This leaves for a lot of wiggle room in how to get to the output, which is essentially what the problem with AI generated code is.

Bad prompt:

"Build me an auth system"

Good prompt:

"Build email/password authentication using the existing User model, store sessions in Redis with 24-hour expiry, and add middleware that protects all routes under /api/protected."

You can see the difference.

Using Plan Mode

You click Shift + Tab twice, and you're in plan mode. Trust me when I say this, this is going to take 5 minutes of your time, but will save you hours upon hours of debugging later on.

Plan mode forces you to think through:

  • What components need to be created or modified
  • How they connect together
  • What the dependencies are
  • What could go wrong

CLAUDE.md Configuration

CLAUDE.md is a markdown file. Markdown is a text format that AI models process extremely well, and Claude in particular handles it better than most other models I've tested.

When you start a Claude Code session, the first thing Claude does is read your CLAUDE.md file. Every instruction in that file shapes how Claude approaches your project. It's essentially onboarding material that Claude reads before every single conversation.

Most people either ignore it completely or stuff it with garbage that makes Claude worse instead of better. There is a threshold where too much or too little information means worse model output.

What Actually Matters

Keep it short. Claude can only reliably follow around 150 to 200 instructions at a time, and Claude Code's system prompt already uses about 50 of those. Every instruction you add competes for attention. If your CLAUDE.md is a novel, Claude will start ignoring things randomly and you won't know which things.

Make it specific to your project. Don't explain what a components folder is. Claude knows what components are. Tell it the weird stuff, like the bash commands that actually matter. Everything that is part of your flow should go into it.

Tell it why, not just what. Claude is a little bit like a human in this way. When you give it the reason behind an instruction, Claude implements it better than if you just tell it what to do.

Approach Example
Okay "Use TypeScript strict mode"
Better "Use TypeScript strict mode because we've had production bugs from implicit any types"

The why gives Claude context for making judgment calls you didn't anticipate. You'll be surprised how effective this actually is.

Update it constantly. Press the # key while you're working and Claude will add instructions to your CLAUDE.md automatically. Every time you find yourself correcting Claude on the same thing twice, that's a signal it should be in the file. Over time your CLAUDE.md becomes a living document of how your codebase actually works.

Good vs Bad CLAUDE.md

Bad CLAUDE.md looks like documentation written for a new hire. Good CLAUDE.md looks like notes you'd leave yourself if you knew you'd have amnesia tomorrow.


Understanding Context Windows

For example, Opus 4.5 has a 200,000 token context window. But here's what most people don't realize: the model starts to deteriorate way before you hit 100% (this varies depending on whether you use through API vs desktop app).

At around 20-40% context usage is where the quality of the output starts to chip away, even if not significantly. If you've ever experienced Claude Code compacting and then still giving you terrible output afterwards, that's why. The model was already degraded before the compaction happened, and compaction doesn't magically restore quality. (Hit /compact for compacting)

Every message you send, every file Claude reads, every piece of code it generates, every tool result—all of it accumulates. And once quality starts dropping, more context makes it worse, not better.

Managing Context Effectively

Scope your conversations. One conversation per feature or task. Don't use the same conversation to build your auth system and then also refactor your database layer. The contexts will bleed together and Claude will get confused. I know at least one of you reading this is guilty of that.

Use external memory. If you're working on something complex, have Claude write plans and progress to actual files (I use SCRATCHPAD.md or plan.md). These persist across sessions. When you come back tomorrow, Claude can read the file and pick up where you left off instead of starting from zero. Sidenote: if you have a hierarchy system of files, keeping these at the very top is how you can get these to work for every task/feature that you decide to build out.

The copy-paste reset. This is a trick I use constantly. When context gets bloated, I copy everything important from the terminal, run /compact to get a summary, then /clear the context entirely, and paste back in only what matters. Fresh context with the critical information preserved. Way better than letting Claude struggle through degraded context.

Know when to clear. If a conversation has gone off the rails or accumulated a bunch of irrelevant context, just /clear and start fresh. It's better than trying to work through confusion. Claude will still have your CLAUDE.md, so you're not losing your project context. Nine times out of ten, using clear is actually better than not using it, as counterintuitive as that sounds.

The mental model that works: Claude is stateless. Every conversation starts from nothing except what you explicitly give it. Plan accordingly.


Prompting Techniques

People spend weeks learning frameworks and tools. They spend zero time learning how to communicate with the thing that's actually generating their code.

Prompting isn't some mystical art. It's probably the most fundamental form of communication there is. And like any communication, being clear gets you better results than being vague. Every. Single. Time.

What Actually Helps

Be specific about what you want. "Build an auth system" gives Claude creative freedom it will use poorly. "Build email/password authentication using this existing User model, store sessions in Redis, and add middleware that protects routes under /api/protected" gives Claude a clear target. Even this is still not perfect.

Tell it what NOT to do. Claude has tendencies. Claude 4.5 in particular likes to overengineer—extra files, unnecessary abstractions, flexibility you didn't ask for. If you want something minimal, say "Keep this simple. Don't add abstractions I didn't ask for. One file if possible." Also, always cross-reference what Claude produces because you don't want to end up having technical debt, especially if you're building something super simple and it ends up building 12 different files for a task that realistically could have been fixed with a couple of lines of code.

Give it context about why. "We need this to be fast because it runs on every request" changes how Claude approaches the problem. "This is a prototype we'll throw away" changes what tradeoffs make sense. Claude can't read your mind about constraints you haven't mentioned.

Something that you have to remember is that AI is designed to speed us up and not completely replace us, especially in the era of very professional software engineering. Claude still makes mistakes. I'm sure that it will keep making mistakes, even if it's going to get better over time. So being able to recognize these mistakes will actually solve a lot of your problems.

Remember: output is everything, but it only comes from input. If your output sucks, your input sucked. There's no way around this.


Model Selection

People blame the model when they get bad results. "Claude isn't smart enough" or "I need a better model."

Reality check: you suck. If you're getting bad output with a good model like Opus 4.5, that means your input and your prompting sucks. Full stop.

The model matters. A lot, actually. But model quality is table stakes at this point. The bottleneck is almost always on the human side: how you structure your prompts, how you provide context, how clearly you communicate what you actually want.

Getting Better at Input

If you're consistently getting bad results, the fix isn't switching models. The fix is getting better at:

  • How you write prompts. Specific > vague. Constraints > open-ended. Examples > descriptions.
  • How you structure requests. Break complex tasks into steps. Get agreement on architecture before implementation. Review outputs and iterate.
  • How you provide context. What does Claude need to know to do this well? What assumptions are you making that Claude can't see?

Model Differences

Model Characteristics Best For
Sonnet Faster and cheaper Execution tasks where the path is clear—writing boilerplate, refactoring based on a specific plan, implementing features where you've already made the architectural decisions
Opus Slower and more expensive Complex reasoning, planning, and tasks where you need Claude to think deeply about tradeoffs

A workflow that works: Use Opus to plan and make architectural decisions, then switch to Sonnet (Shift+Tab in Claude Code) for implementation. This will depend on your task—sometimes you can use Opus 4.5 for implementation as well. Think about selling your kidney if you're doing this through the API usage tab though. Your CLAUDE.md ensures both models operate under the same constraints, so the handoff is clean.


Skills and Slash Commands

Skills are reusable instructions that extend Claude's capabilities through custom slash commands. They're one of the most underutilized features in Claude Code.

What Skills Do

  • Run automatically based on task context, or manually via /skill-name
  • Restrict which tools Claude can use within the skill
  • Accept arguments for dynamic behavior
  • Run in isolated contexts to keep your main conversation clean

Setting Up Skills

Skills are stored as SKILL.md files with YAML frontmatter:

Location: .claude/skills/ (project) or ~/.claude/skills/ (personal)

---
name: code-review
description: Reviews code for best practices and potential issues
allowed-tools: Read, Grep, Glob
---

When reviewing code, check for:
1. Code organization and structure
2. Error handling patterns
3. Security concerns
4. Performance implications

Common Skill Patterns

Manual slash commands for controlled operations:

---
name: deploy
description: Deploy to staging environment
allowed-tools: Bash, Read
---

Deploy the current branch to staging:
1. Run tests first
2. Build the project
3. Deploy using deploy.sh

Automatic skills for background knowledge:

---
name: project-conventions
description: This project's coding conventions
disable-model-invocation: false
---

- Use functional components with hooks
- Error boundaries around all routes
- All API calls go through the api/ directory

Using Arguments

Skills can accept dynamic input using $ARGUMENTS:

---
name: test-file
description: Run tests for a specific file
allowed-tools: Bash, Read
---

Run tests for the file: $ARGUMENTS
Use the appropriate test runner based on file extension.

Usage: /test-file src/components/Button.tsx


Subagents

Subagents are isolated AI assistants that handle specific tasks in their own context window. They're essential for keeping your main conversation clean and focused.

Why Use Subagents

  • Context isolation: Verbose operations (like running tests) don't clutter your main conversation
  • Specialized tools: Restrict what tools an agent can use
  • Different models: Route simple tasks to cheaper models like Haiku
  • Parallel work: Spawn multiple agents for concurrent investigation

Built-in Subagents

Agent Purpose Tools
Explore Fast codebase searching and analysis Read-only tools
Plan Research during plan mode Read-only tools
General-purpose Complex multi-step tasks All tools
Bash Terminal commands in separate context Bash only

Creating Custom Subagents

Store in .claude/agents/ as markdown files:

---
name: test-runner
description: Runs tests and reports results
tools: Bash, Read, Grep
model: haiku
---

You are a test execution specialist. When asked to run tests:
1. Identify the test framework
2. Run the appropriate test command
3. Parse and summarize results
4. Report any failures with file:line references

Common Patterns

Isolate verbose output:

Run the test suite using the test-runner agent

The test output stays in the subagent's context, and you get a clean summary.

Parallel research:

In parallel, use explore agents to find:
1. Where authentication is handled
2. Where database connections are configured
3. Where logging is set up

Enforce read-only exploration: Custom agents with only Read, Grep, Glob tools can't accidentally modify anything.


Plugins

Plugins are packaged, versioned extensions containing skills, agents, hooks, and MCP servers. They're how you share functionality across teams.

Plugin Structure

my-plugin/
├── .claude-plugin/
│   └── plugin.json          # Manifest (required)
├── skills/                   # Custom skills
│   └── my-skill/
│       └── SKILL.md
├── agents/                   # Custom subagents
│   └── my-agent.md
├── hooks/                    # Event handlers
│   └── hooks.json
└── .mcp.json                 # MCP servers

The Manifest

{
  "name": "my-team-tools",
  "version": "1.0.0",
  "description": "Shared tooling for the team",
  "author": "Your Team"
}

Using Plugins

# Install from marketplace
/plugin install plugin-name

# Load locally for testing
claude --plugin-dir ./my-plugin

Skills from plugins are namespaced: /plugin-name:skill-name

When to Use Plugins

  • Sharing functionality with your team
  • Distributing through marketplaces
  • Bundling related skills, agents, hooks, and MCP servers together
  • Versioned releases and updates

MCP (Model Context Protocol)

MCP lets Claude connect to external services—Slack, GitHub, databases, APIs. If you find yourself constantly copying information from one place into Claude, there's probably an MCP server that can do it automatically.

Adding MCP Servers

HTTP servers (recommended):

claude mcp add --transport http github https://api.githubcopilot.com/mcp/
claude mcp add --transport http sentry https://mcp.sentry.dev/mcp

Local servers:

claude mcp add --transport stdio postgres \
  --env DSN=postgresql://user:pass@host/db \
  -- npx -y @bytebase/dbhub

Configuration Scopes

Scope Location Sharing
Project .mcp.json Version controlled, shared with team
User ~/.claude.json Personal, across all projects
Local ~/.claude.json (project section) Personal, project-specific

Configuration Example

.mcp.json in your project root:

{
  "mcpServers": {
    "github": {
      "type": "http",
      "url": "https://api.githubcopilot.com/mcp/"
    },
    "postgres": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@bytebase/dbhub"],
      "env": {
        "DSN": "postgresql://user:pass@localhost/mydb"
      }
    }
  }
}

Managing MCP

claude mcp list              # View all configured servers
claude mcp get github        # Get details for specific server
claude mcp remove github     # Remove a server
/mcp                         # Authenticate in Claude Code

Popular MCP Servers

  • GitHub: Code reviews, PR management, issues
  • Sentry: Error monitoring and debugging
  • PostgreSQL/databases: Direct data queries
  • Slack: Messaging and notifications
  • Notion: Documentation and notes

There's a ton of MCP marketplaces, and if there is not an MCP for your use case, you can create your own MCP server for whatever tool you need. I will be very surprised if you find one that doesn't exist though.


Hooks

Hooks let you run code automatically before or after Claude makes changes. This is deterministic control—it happens every time, not just when Claude remembers.

Hook Events

Event When It Runs Use Case
PreToolUse Before tool calls Block operations, validate
PostToolUse After tool calls Format code, run linters
UserPromptSubmit User submits prompt Validate input
Stop Claude finishes Cleanup, notifications
SessionStart Session begins Setup environment
SessionEnd Session ends Save state

Configuration

Store in .claude/hooks/hooks.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "npx prettier --write \"$FILE_PATH\""
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"$(date): $TOOL_INPUT\" >> ~/.claude-bash-log.txt"
          }
        ]
      }
    ]
  }
}

Common Use Cases

Auto-format on every edit:

{
  "matcher": "Edit|Write",
  "hooks": [{
    "type": "command",
    "command": "npx prettier --write \"$FILE_PATH\""
  }]
}

Type-check after edits:

{
  "matcher": "Edit|Write",
  "hooks": [{
    "type": "command",
    "command": "npx tsc --noEmit"
  }]
}

Block sensitive files:

{
  "matcher": "Edit|Write",
  "hooks": [{
    "type": "command",
    "command": "python3 -c \"import sys; sys.exit(2 if '.env' in '$FILE_PATH' else 0)\""
  }]
}

This is actually what helps remove technical debt as well. If you set a specific hook after every edit, you have a security feature potentially cleaning up your code. Should be very helpful when Claude reviews your PRs.


When Claude Gets Stuck

Sometimes Claude just loops. It tries the same thing, fails, tries again, fails, and keeps going. Or it confidently implements something that's completely wrong and you spend twenty minutes trying to explain why.

When this happens, the instinct is to keep pushing. More instructions. More corrections. More context. But the reality is that the better move is just to change the approach entirely.

Recovery Strategies

Start simple—clear the conversation. The accumulated context might be confusing it. /clear gives you a fresh start.

Simplify the task. If Claude is struggling with a complex task, break it into smaller pieces. Get each piece working before combining them. But in reality, if Claude is struggling with a complex task, that means that your plan mode is insufficient.

Show instead of tell. If Claude keeps misunderstanding what you want, write a minimal example yourself. "Here's what the output should look like. Now apply this pattern to the rest." Claude is extremely good at understanding what success metrics look like and actually being able to follow them.

Be creative. Try a different angle. Sometimes the way you framed the problem doesn't map well to how Claude thinks. Reframing—"implement this as a state machine" vs "handle these transitions"—can unlock progress.

The meta-skill here is recognizing when you're in a loop early. If you've explained the same thing three times and Claude still isn't getting it, more explaining won't help. Change something.


Building Automated Systems

The people who get the most value from Claude aren't using it for one-off tasks. They're building systems where Claude is a component.

Headless Mode

Claude Code has a -p flag for headless mode. It runs your prompt and outputs the result without entering the interactive interface. This means you can script it. Pipe output to other tools. Chain it with bash commands. Integrate it into automated workflows.

claude -p "Review this PR and list any security concerns" < pr_diff.txt

Enterprise Use Cases

  • Automatic PR reviews
  • Automatic support ticket responses
  • Automatic logging and documentation updates
  • All of it logged, auditable, and improving over time

The Improvement Flywheel

Claude makes a mistake → you review the logs → you improve the CLAUDE.md or tooling → Claude gets better next time.

This compounds. Right now I'm in the process of being able to have Claude already improve its own CLAUDE.md files. After months of iteration, systems built this way are meaningfully better than they were at launch—same models, just better configured.

If you're only using Claude interactively, you're leaving value on the table. Think about where in your workflow Claude could run without you watching.


Summary

Principle Key Takeaway
Think first Planning produces dramatically better results than just starting to talk
CLAUDE.md Keep it short, specific, tell it why, and update constantly
Context Degrades at 30%, not 100%. Use external memory, scope conversations, and use the copy-paste reset trick
Architecture You cannot skip planning. If you don't think through structure first, output will be bad
Input quality If you're getting bad results with a good model, your prompting needs work
Skills Create reusable workflows and slash commands for repeated tasks
Subagents Isolate verbose operations and specialize tool access
Plugins Package and share functionality across teams
MCP Connect external services and databases
Hooks Automate formatting, validation, and security controls
When stuck Don't loop. Clear, simplify, show, reframe
Systems Build automated workflows with headless mode

If you're building with Claude—whether it's your own projects or production systems—these are the things that determine whether you're fighting the tool or flowing with it.


Additional Resources

  • Press /help in Claude Code for built-in documentation
  • Use # to add instructions to your CLAUDE.md on the fly
  • Shift+Tab to toggle between models
  • /compact to summarize and reduce context
  • /clear to start fresh

If you have the Pro Max plan (I pay the $200/month), why not try everything Claude has to offer? See what works and what doesn't. You're paying for it anyway.

And here's the thing: don't get shut off if something doesn't work on the first try. These models are improving basically every week. Something that didn't work a month ago might work now. Being an early adopter means staying curious and re-testing things.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment