I’ve been using Claude Code daily for months. The first few weeks were rough. It’d reformat files I didn’t ask it to touch, add “helpful” comments everywhere, hallucinate function names, and confidently break things I never asked it to change.

The tool itself is good. The defaults are not enough. Here’s what actually fixed it.

CLAUDE.md is the whole game

Create a CLAUDE.md file in your project root. If you do nothing else from this post, do this one. Claude Code reads it at the start of every session: your stack, your commands, your conventions, and most importantly, what NOT to do.

It’s like onboarding a new teammate who’s extremely talented but has zero context about your project. You wouldn’t let them start pushing code on day one without telling them how things work around here. Same energy.

Here’s a stripped-down version of mine:

# My Project

Astro + TypeScript, deployed to Cloudflare Pages.

## Stack
- Astro (static output, content collections)
- Hand-crafted CSS — no Tailwind, no component libraries
- Vanilla JS only — no frameworks on the client

## Commands
npm run dev      # dev server
npm run build    # production build

## Rules
- Don't add comments unless the WHY is non-obvious
- No drive-by refactors — change only what was asked
- No new files unless explicitly required

That Rules section at the bottom? That’s the one doing the heavy lifting. Without it, Claude will “improve” your code every chance it gets. You ask it to fix a bug and it comes back with the fix plus three extracted helpers, renamed variables, and a new utility file. Nobody asked for that. The CLAUDE.md is where you tell it to chill.

You can also tell it to do things in order, like “before fixing a bug, figure out why it’s broken first” or “after making changes, verify the feature actually works.” Without this, Claude skips straight to the fix like a shonen protagonist charging into battle without a plan. Sometimes that works. Usually it doesn’t.

There’s also a global one at ~/.claude/CLAUDE.md that applies to every project. I keep my global one for cross-project stuff: “don’t add emojis”, “keep responses short”, ordering rules. The project CLAUDE.md stays specific to the stack.

Rules that load themselves

~/.claude/rules/ is a directory where you drop markdown files and they load automatically. No imports, no wiring.

I have a code-quality.md that’s basically my engineering opinions written down:

# Code Quality

## Guiding bias: restraint

The best code is the simplest code that fully solves 
the actual problem.

- No premature abstraction — one concrete implementation 
  stays concrete
- DRY with judgment — two pieces of code that look alike 
  but change for different reasons are NOT duplication
- YAGNI — implement only what the current requirement needs
- Name for intent — approveApplication() over 
  updateStatusAndSendEmail()

Before this existed, every session was the same argument. “No, don’t extract that into a helper.” “No, I don’t need an interface for one implementation.” “No, three similar lines is fine.” Now it reads the file and stops fighting me.

You can scope rules per project too. .claude/rules/ in your project directory instead of the global one.

Memory that survives sessions

Claude Code writes small markdown files to ~/.claude/projects/<your-project>/memory/ and indexes them in a MEMORY.md. Next session, it reads the index and picks up where it left off.

Three types worth caring about:

User memories. Who you are, what you know. I mentioned once that I’m a backend engineer. Now it doesn’t explain DynamoDB queries to me like I’m five, but it does walk me through React hooks when I’m poking at frontend code. Said it once, never again.

Feedback memories. This one changed everything. Think of the first few sessions like early training arc episodes. Claude keeps making the same mistakes, you keep correcting it. “Don’t extract helpers unless I ask.” “Stop adding try-catch blocks everywhere.” Each correction gets saved as a feedback memory. By week two, the training arc is over. Claude’s absorbed your preferences and you barely correct anything. The memory folder is doing the work.

Project memories. Deadlines, decisions, who owns what. Context that lives in Slack threads and people’s heads, not in the code.

These are just markdown files with YAML frontmatter sitting in a folder. You can open them, edit them, delete the stale ones. Not a black box.

---
name: writing-voice
description: user's blog writing style preferences
metadata:
  type: user
---

Conversational, direct. Uses analogies from anime and 
daily life. Anti-AI-slop. From Nepal.

If Claude starts acting weird or referencing stuff that’s no longer true, check the memory folder. Half the time there’s something from three weeks ago that’s poisoning its decisions.

Custom commands for things you repeat

~/.claude/commands/ is where custom slash commands live. Each markdown file becomes one.

I have a /verify command that checks whether Claude actually achieved the goal, not just completed the steps. “I edited the files” and “the feature works” are different sentences.

<!-- ~/.claude/commands/verify.md -->
Verify the goal was achieved, not just that tasks 
were completed. Check:
- Does the feature work end-to-end?
- Did anything else break?
- Run the tests if they exist.

If you’ve typed the same instruction more than twice, make it a command.

Hooks for guardrails

Hooks are shell scripts that fire before or after Claude does things. You configure them in ~/.claude/settings.json.

The three I’d never remove:

Format-on-edit. Runs Prettier after every file edit, but only on the lines Claude actually changed. Not the whole file. This matters more than it sounds. Without it, your PR diff is 80% formatting noise from lines Claude never meant to touch, and your reviewer assumes you went through the file with a machete.

Commit validation. Enforces conventional commits. Left alone, Claude writes commit messages like it’s narrating a documentary. A hook that rejects anything over one line fixes that fast.

Destructive command guard. Blocks git push --force, rm -rf, and friends unless you explicitly say yes. Claude occasionally gets a little too confident with irreversible operations. Two seconds to confirm beats two hours to recover.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "command": "node ~/.claude/hooks/git-destructive-guard.js"
      }
    ]
  }
}

Stop approving the same thing

By default, Claude asks permission before running any command. Every git status. Every ls. Every grep. Forty times a session. The default is correct. You don’t want an AI running arbitrary commands unsupervised. But clicking “approve” on git status for the fortieth time makes you want to close your laptop and take up farming.

Permissions allowlist in ~/.claude/settings.json:

{
  "permissions": {
    "allow": [
      "Bash(git status:*)",
      "Bash(git diff:*)",
      "Bash(git log:*)",
      "Bash(ls:*)",
      "Bash(grep:*)",
      "Bash(find:*)",
      "Bash(npm run:*)",
      "Bash(npm test:*)",
      "Read(**)"
    ]
  }
}

Safe, read-only stuff gets pre-approved. Anything destructive (git push, rm, installing packages) still needs your sign-off. Start with the obvious ones and add more every time you catch yourself clicking “approve” on muscle memory.

The stuff nobody tells you

Your CLAUDE.md should be at least half “don’t” statements. Claude is trained to be helpful. Unconstrained helpfulness means adding error handling nobody asked for, building abstractions for requirements that don’t exist, and writing comments that just repeat what the code already says. Tell it what to stop doing. Loudly.

Start sessions clean. When Claude’s answers start getting worse mid-session, the context window is full. Don’t fight it. New session. I’ve lost more time to context pollution than I want to admit.

Scope everything. My number one correction, for weeks, was Claude touching files outside what I asked for. Fixed a function and also renamed a variable three files away. Refactored a component and also “cleaned up” the imports in a file I never mentioned. Now it’s a hard rule in my CLAUDE.md: every changed line must serve the request. Spot something unrelated that needs fixing? Write it down and move on.

Read the diff, not the chat. Claude will tell you it did exactly what you asked. Sometimes it did something slightly different. git diff doesn’t lie. Claude’s explanation sometimes does.

Start here

If this feels like a lot, three things:

  1. Create a CLAUDE.md in your project with your stack, commands, three “don’t do this” rules
  2. Drop a code-quality.md in ~/.claude/rules/ with your engineering opinions
  3. Add a permissions allowlist to ~/.claude/settings.json so you stop approving git status

Run it for a week. You’ll correct Claude a bunch at first. Each correction becomes a feedback memory or a new rule, and you never make that correction again. By week two, it knows how you work.

None of this is a perfect system. It’s just configuration, same as your editor, your linter, your git hooks. You tell the tool how you work, and it stops fighting you.