Local-First Claude Code: CLAUDE.md & a Secure CLI

9 min readYaseen Khatib · MERN + AI Architect

Every new Claude Code session starts the same way: you re-explain that the project uses pnpm not npm, that tests run with Vitest, that the API layer lives under src/server and must never import from src/ui. You're paying — in tokens and in your own time — to re-teach the same facts daily, and the one session where you forget is the one where the agent runs npm install and rewrites your lockfile. The local agent needs a standing memory and a hard boundary. Both are configuration, not prompting.

Core Architectural Concepts & Trade-offs

CLAUDE.md is the agent's standing system prompt for a workspace. It loads automatically at the start of every session, so any fact you put there is amortized to near-zero cost (Lesson 9 — it caches) instead of re-paid per conversation. It resolves in a hierarchy: a global ~/.claude/CLAUDE.md for cross-project rules, a project CLAUDE.md checked into the repo for team conventions, and local overrides — merged, with the more specific file winning. This is the difference between an agent that re-learns your project every morning and one that already knows it.

The discipline is that a CLAUDE.md is a context-window resident, so it is subject to the same budget physics as everything else. A bloated one — every edge case, a wall of history, duplicated docs — is dead weight re-sent on every turn, and worse, it buries the load-bearing rules in noise the model skims past. Treat it like API design: terse, imperative, the few invariants that actually change behavior. "Use pnpm. Tests: pnpm test. Never edit dist/." beats three paragraphs of prose every time.

The second pillar is the permission boundary. The agent can run shell commands, and the model is an untrusted planner (Lesson 4) — so the CLI is an execution surface that needs an explicit allowlist, not blanket trust. Claude Code gates tool use behind a permission model: read-only and safe commands can be pre-approved, while destructive or outward-facing operations require confirmation. Encoding that allowlist in settings turns "hope it doesn't" into "it can't" — the agent physically cannot run git push --force if it isn't permitted.

The two pillars reinforce each other. CLAUDE.md tells the agent what to do and the conventions to honor; the permission layer enforces what it may never do regardless of what it plans. Hooks close the loop with determinism — a pre-commit hook or a command-blocking hook is a guardrail the model can't talk its way past, which (Lesson 10) is the only kind of guardrail that survives an adversarial input. Configuration over correction: the cheapest mistake is the one the runtime makes impossible.

An Optimized CLAUDE.md

Terse, imperative, only the invariants that change behavior. Every line earns its place in the window.

CLAUDE.md
# Project: shop-api

## Stack
- Package manager: **pnpm** (never npm/yarn — it breaks the lockfile).
- Tests: `pnpm test` (Vitest). Typecheck: `pnpm typecheck`.
- Run all three before reporting any change done.

## Architecture (hard boundaries)
- API + business logic: `src/server/**`
- UI: `src/ui/**`
- **`src/server` must never import from `src/ui`** (enforced by eslint
  boundary rule — do not disable it).
- DB access only through `src/server/db/repo.ts`. No inline SQL elsewhere.

## Conventions
- Validate all external input with Zod at the route boundary.
- No new files unless asked; prefer editing existing ones.
- `dist/` and `*.lock` are generated — never hand-edit.

## Don't
- Don't add dependencies without asking.
- Don't write comments that restate the code.

Pair it with a permission allowlist in .claude/settings.json so safe commands run uninterrupted while anything destructive needs an explicit yes.

.claude/settings.json
{
  "permissions": {
    "allow": [
      "Bash(pnpm test)",
      "Bash(pnpm typecheck)",
      "Bash(git status)",
      "Bash(git diff:*)"
    ],
    "deny": [
      "Bash(git push --force:*)",
      "Bash(rm -rf:*)",
      "Bash(pnpm publish:*)"
    ]
  }
}

Layered Context & the Permission Gate

CLAUDE.md hierarchy~/.claude/CLAUDE.mdglobal rules./CLAUDE.mdproject conventions.claude/locallocal overridesClaude Codestanding context loadedpermission gateallowlist / denylist+ hookspnpm test ✓rm -rf ✗
CLAUDE.md is the standing memory; the permission gate is the hard boundary. Configuration over correction.

A configured workspace is the launchpad for reusable automation. Next, turn a repeated workflow into a first-class capability: building custom Claude Skills. Or browse the full roadmap.

Looking to architect a similar system?

Let's ship it at AI-speed.

Start a conversation →