← Back to writing

You're Using Claude Code. You're Not Really Using Claude Code.

claude-codeaideveloper-productivitydeveloper-tools

You installed Claude Code. You type prompts. It writes code. You accept, tweak, commit.

That’s fine. It works. But you’re using a flight simulator as a chair.

Claude Code is a development platform with a configuration system, an automation layer, a plugin architecture, and an agent framework. Most of this ships out of the box. Almost nobody uses it. I didn’t either, until I started pulling threads and realized how much I was leaving on the table.

This is the primer I wish I’d had on day one.

CLAUDE.md: teach Claude how you work

Every project has conventions that aren’t written down. Naming patterns, testing philosophy, architectural boundaries, import ordering, the things a new developer gets wrong on their first PR.

CLAUDE.md is where you encode those decisions. Drop it in your project root and Claude reads it at the start of every session.

# CLAUDE.md

## Architecture
- Clean Architecture: Domain → Application → Infrastructure → WebApi
- Domain models are always persistence-ignorant
- No business logic in controllers or handlers

## Testing
- Unit tests mock repositories, never hit the database
- Integration tests use Testcontainers with SQL Server
- Every handler gets three tests: success, not-found, validation failure

## Style
- No region blocks
- No comments that restate the code
- Prefer guard clauses over nested ifs

This is the difference between “reasonable code” and “your code.” Without it, Claude produces generic output. With it, Claude produces code that passes your PR review.

Scoped rules

CLAUDE.md works at the project level. But if your frontend and backend have different conventions, a single file gets messy.

The .claude/rules/ directory lets you split rules by topic and scope them to file paths:

<!-- .claude/rules/api-conventions.md -->
---
paths:
  - "src/api/**/*.ts"
  - "src/middleware/**/*.ts"
---

# API Conventions

- All endpoints return consistent envelope: { data, error, meta }
- Use zod for request validation at the controller level
- Never throw from a handler. Return Result<T, Error> instead.
<!-- .claude/rules/react-components.md -->
---
paths:
  - "src/components/**/*.tsx"
  - "src/pages/**/*.tsx"
---

# React Conventions

- Functional components only. No class components.
- Co-locate styles: Button.tsx + Button.module.css
- Props interfaces named {Component}Props, exported above component

Claude loads the right rules when it touches matching files. Your API rules don’t pollute your frontend context. Your frontend rules don’t confuse your backend work.

Plugins: the easiest wins

Plugins bundle skills, agents, hooks, and MCP servers into installable packages. They’re the fastest way to level up because someone else did the work. You just install.

Run /plugin to open the plugin manager. You get four tabs: Discover, Installed, Marketplaces, and Errors.

Code intelligence plugins

Install these first. They connect Claude to Language Server Protocol (LSP) servers, the same technology that powers VS Code’s code intelligence. After installing, Claude can jump to definitions, find references, see type errors immediately after edits, and fix them in the same turn.

Available for: TypeScript, Python, Rust, Go, C#, Java, Kotlin, PHP, Swift, C/C++, Lua.

/plugin install typescript-lsp@claude-plugins-official
/plugin install pyright-lsp@claude-plugins-official
/plugin install csharp-lsp@claude-plugins-official

The plugin needs the language server binary on your system. If you already have typescript-language-server or pyright-langserver installed (you probably do if you use VS Code), it just works. Claude will often prompt you to install the right plugin when you open a project.

The difference is real. Without LSP, Claude edits a file and hopes it compiles. With LSP, Claude edits a file, sees the type error it just introduced, and fixes it before you even notice.

External integration plugins

Pre-configured MCP servers, packaged so you don’t have to write config files:

/plugin install github@claude-plugins-official
/plugin install linear@claude-plugins-official
/plugin install slack@claude-plugins-official
/plugin install sentry@claude-plugins-official

Also available: Jira/Confluence (atlassian), Asana, Notion, Figma, Vercel, Firebase, Supabase, GitLab.

After installing the GitHub plugin, Claude can read issues, review PRs, check CI status, and create branches without you leaving the terminal.

Workflow plugins

These add commands for common dev tasks:

/plugin install commit-commands@claude-plugins-official
/plugin install pr-review-toolkit@claude-plugins-official

Plugin commands are namespaced. After installing commit-commands, you get /commit-commands:commit, which stages changes, generates a message, and commits. One command instead of the usual back-and-forth.

Marketplaces

The official Anthropic marketplace is available by default. You can add others from GitHub repos, GitLab, or local directories:

# GitHub marketplace
/plugin marketplace add anthropics/claude-code

# GitLab or any git host
/plugin marketplace add https://gitlab.com/your-org/claude-plugins.git

# Local directory (useful for team-internal plugins)
/plugin marketplace add ./our-team-plugins

For teams, add your marketplace to .claude/settings.json so everyone gets it automatically:

{
  "extraKnownMarketplaces": {
    "our-team-tools": {
      "source": {
        "source": "github",
        "repo": "your-org/claude-plugins"
      }
    }
  }
}

Plugin scopes

When installing, you choose where the plugin lives:

  • User: installed for you across all projects
  • Project: added to .claude/settings.json, shared with your team via git
  • Local: just you, just this repo

Skills: reusable workflows, not repeated prompts

You’ve typed the same kind of prompt before. “Create a new API endpoint for X with tests.” “Review this file for security issues.” “Generate a migration for this schema change.”

Skills turn those repeated patterns into reusable commands. Create a file at .claude/skills/<name>/SKILL.md and invoke it with /<name>.

<!-- .claude/skills/new-endpoint/SKILL.md -->
---
name: new-endpoint
description: Scaffold a new API endpoint with handler, validator, and tests
allowed-tools: Read, Write, Edit, Glob, Grep
---

Create a new API endpoint based on the user's description.

Follow the project's Clean Architecture pattern:
1. Create the request/response DTOs in Application/Features/{Feature}/
2. Create the handler with MediatR IRequestHandler
3. Create the FluentValidation validator
4. Create the controller action in WebApi/Controllers/
5. Create three unit tests: success, not-found, validation failure
6. Create one integration test hitting the real endpoint

Use the existing endpoint at `src/Application/Features/Boards/GetBoard/`
as the reference implementation.

The feature name is: $ARGUMENTS

Now /new-endpoint CreateTeam scaffolds the full vertical slice. Same patterns every time. No re-explaining.

Dynamic context injection

Skills can pull in live data from your project using the !`command` syntax:

<!-- .claude/skills/review-migrations/SKILL.md -->
---
name: review-migrations
description: Review pending database migrations for safety issues
---

Here are the pending migrations:

!`find src/Infrastructure/Migrations -name "*.cs" -newer .last-migration-review`

Review each migration for:
- Destructive operations (DROP, DELETE, TRUNCATE)
- Missing rollback support
- Index changes on large tables
- Data type changes that could lose precision

Report findings in a table: file, issue, severity, recommendation.

The shell command runs when the skill executes, and its output becomes part of the prompt. Your skill always works with current data.

Hooks: automate the tedious parts

Hooks are shell commands that fire at specific points in Claude’s lifecycle. Think of them as git hooks, but for your AI workflow.

Configure them in .claude/settings.json or interactively with /hooks.

Auto-format on every edit

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/format.sh"
          }
        ]
      }
    ]
  }
}
#!/bin/bash
# .claude/hooks/format.sh
FILE=$(jq -r '.tool_input.file_path')
npx prettier --write "$FILE" 2>/dev/null
exit 0

Claude edits a file. Prettier formats it. You never see unformatted code. You never ask Claude to “fix the formatting.”

Block dangerous commands

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/guard.sh"
          }
        ]
      }
    ]
  }
}
#!/bin/bash
# .claude/hooks/guard.sh
CMD=$(jq -r '.tool_input.command')

if echo "$CMD" | grep -qE "rm -rf|drop table|--force|--no-verify"; then
  echo "Blocked: destructive command" >&2
  exit 2  # exit 2 = block the tool call
fi

exit 0  # exit 0 = allow

Claude sees the block message and adjusts its approach. You don’t have to watch every command it runs.

Survive context compaction

Long sessions hit context limits. Claude compacts the conversation to free space. Critical reminders can get lost.

{
  "hooks": {
    "PreCompact": [
      {
        "matcher": ".*",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'REMEMBER: We are refactoring the auth module. New code goes in src/auth/v2/. Do not modify src/legacy/.'"
          }
        ]
      }
    ]
  }
}

The message gets injected right before compaction, so it survives into the compressed context.

Other useful hook events

Hooks aren’t limited to tool use and compaction. A few more worth knowing:

  • SessionStart: run setup commands when a session begins (or resumes, or after /clear)
  • Notification: trigger a desktop notification when Claude needs your attention
  • SubagentStart / SubagentStop: log or validate subagent activity
  • Stop: run something after Claude finishes responding (linting, notifications, logging)

Custom subagents: specialists on your team

Claude can spawn subagents for specific tasks. The built-in ones (Explore, Plan) are useful. Custom ones are better.

Define them in .claude/agents/<name>.md:

<!-- .claude/agents/security-reviewer.md -->
---
name: security-reviewer
description: Reviews code changes for security vulnerabilities
tools: Read, Grep, Glob, Bash
disallowedTools: Write, Edit
model: sonnet
maxTurns: 15
---

You are a security reviewer. Analyze the code for:

- SQL injection (raw queries, string concatenation in queries)
- XSS (unescaped output, dangerouslySetInnerHTML)
- Auth bypass (missing middleware, unchecked roles)
- Secret exposure (hardcoded keys, logged credentials)
- Insecure deserialization
- Path traversal

Report findings as:

| File | Line | Issue | Severity | Fix |
|------|------|-------|----------|-----|

If you find nothing, say so. Do not invent issues.

The disallowedTools: Write, Edit part matters. This agent can read everything but change nothing. It’s a reviewer, not a fixer.

Agents with persistent memory

---
name: code-reviewer
description: Reviews PRs with learned team preferences
tools: Read, Grep, Glob
memory: user
---

The memory: user setting lets this agent remember corrections across sessions. Tell it “we don’t flag single-letter variables in loop counters” once. It remembers.

Agents in isolated worktrees

---
name: experiment-runner
description: Tests experimental changes without affecting working tree
tools: Read, Write, Edit, Bash, Glob, Grep
isolation: worktree
---

This agent gets its own copy of the repo. It can make changes, run tests, break things. Your working directory stays clean.

MCP servers: connect Claude to everything

The Model Context Protocol lets Claude talk to external tools and services. GitHub, databases, APIs, anything with an MCP server.

If you installed the integration plugins from the plugins section above, you already have MCP servers running. This section is for when you need custom connections or want to understand what’s happening underneath.

Project-level configuration

Drop a .mcp.json in your project root. Your whole team gets the same tools.

{
  "mcpServers": {
    "github": {
      "type": "http",
      "url": "https://api.githubcopilot.com/mcp/"
    },
    "postgres": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-postgres"],
      "env": {
        "DATABASE_URL": "${DATABASE_URL}"
      }
    }
  }
}

Now Claude can query your database, read GitHub issues, and create pull requests without you copy-pasting between tools.

Authentication

Some MCP servers need OAuth. Run /mcp inside a session to authenticate. Credentials are stored locally, not in your project config.

Prompt craft: effective and cost-effective

Everything above is configuration. But the biggest lever is still how you write prompts.

Be specific, not verbose

Bad:

Can you please look at the authentication module and maybe refactor it a bit to make it cleaner and more maintainable? I think there might be some issues with how we’re handling tokens.

Good:

The token refresh logic in src/auth/tokenService.ts retries indefinitely on 401 responses. Add a max retry count of 3 and throw AuthenticationExpiredError when exhausted. Add unit tests.

The first prompt burns tokens on Claude figuring out what you want. The second prompt tells it. Less thinking, less cost, better output.

Use effort levels

Not every task needs deep reasoning. Claude Code supports three effort levels:

  • Low: rename a variable, fix a typo, simple formatting
  • Medium: implement a feature, debug an issue (default)
  • High: complex refactoring, architectural decisions, subtle bugs

Toggle with Cmd+P (or Ctrl+P) and select the effort level. Or include “ultrathink” in your prompt for one-off deep reasoning.

Low effort costs less and responds faster. Use it for the simple stuff.

Manage your context window

Every file Claude reads, every command it runs, every response it generates eats context. When the window fills up, older content gets compressed and nuance gets lost.

Use subagents for research. Instead of asking Claude to “look through the codebase for all usages of UserService,” spawn a subagent. The research happens in a separate context. Only the results come back.

Use /compact proactively. Don’t wait for auto-compaction. If you’re switching tasks mid-session, compact first. You keep a clean summary and reclaim context for the new task.

Use /clear for fresh starts. Switching to a completely different task? Start a new session. Leftover context from a previous task adds noise and cost.

Attach only what’s needed

You can reference files, images, and URLs in prompts. Be selective. Attaching your entire src/ directory “for context” floods the window. Attach the specific files Claude needs.

Look at src/auth/tokenService.ts and src/auth/tokenService.test.ts.
The refresh loop doesn't respect the max retry config. Fix it.

Two files. Clear scope. Claude reads what it needs and nothing else.

The hidden features

These are the ones I see people miss constantly.

/btw for side questions

Need a quick answer without derailing your current task? /btw asks a side question with no tool access. The answer doesn’t pollute your working context.

/btw what's the difference between structuredClone and JSON parse/stringify for deep copying?

/rewind for checkpoints

Claude took a wrong turn three messages ago. Instead of starting over:

/rewind

Pick the checkpoint. You’re back. No lost context, no re-explaining what you were doing.

Shift+Tab to cycle permission modes

Three modes, one shortcut:

  • Default: Claude asks before running tools
  • Accept edits (⏵⏵): auto-accept file changes, still asks for shell commands
  • Plan (): read-only mode, Claude can explore but not modify

Start in Plan mode to explore. Switch to Accept Edits when you trust the direction. Switch back to Default for sensitive operations.

/loop for monitoring

Waiting for a deployment? A long test suite? A CI pipeline?

/loop 5m check if the GitHub Actions workflow on the main branch has completed

Claude checks every 5 minutes and reports back. You go do something else.

Worktrees for parallel work

claude --worktree feature-auth

This creates a separate git worktree with its own Claude session. You can work on two features at the same time without branch switching or stashing.

Keyboard shortcuts you should customize

Run /keybindings to create ~/.claude/keybindings.json:

{
  "bindings": [
    {
      "context": "Chat",
      "bindings": {
        "ctrl+e": "chat:externalEditor",
        "ctrl+s": "chat:stash",
        "alt+t": "chat:thinkingToggle"
      }
    }
  ]
}

Ctrl+E opens your prompt in your editor. Write long prompts in VS Code instead of a single-line input. Ctrl+S stashes your current prompt for later. Alt+T toggles extended thinking.

Session management

Name your sessions:

/rename auth-refactor

Resume later:

/resume

You get a picker showing recent sessions with branch context. Pick up exactly where you left off.

Putting it all together

None of these features exist in isolation. They compound.

CLAUDE.md teaches Claude your conventions. Rules scope those conventions to the right files. Plugins give you code intelligence, integrations, and workflow commands with zero configuration. Skills encode your repeatable workflows. Hooks automate the friction between steps. Subagents run specialized tasks without polluting your context. MCP servers connect your tools. And good prompt habits make all of it cheaper and faster.

You don’t need to adopt everything at once. Start with CLAUDE.md. That alone changes the quality of output. Install a few plugins. Add rules when your single file gets too long. Build your first skill when you catch yourself typing the same prompt for the third time. Add a hook when you’re tired of manually formatting code after every edit.

Each layer you add makes Claude Code less of a chatbot and more of a teammate that knows how you work.

The tool is already installed. Now use it.

Found this useful?

If this post helped you, consider buying me a coffee.

Buy me a coffee

Comments