Claude Code attaches to a repository you already have. There is no migration, no index to build and no directory layout it expects.
The work is in three files: a CLAUDE.md that describes how your project
actually builds, a .claude/settings.json that says which commands may run
without asking, and a hook that runs your typechecker after every edit.
What this setup gives you
A session that can run bun run typecheck and bun test on its own, read and
edit anything under src/, and refuse to touch .env, dist/ or your git
remote without an explicit prompt.
It also gives you diffs small enough to actually review. That is the part most teams skip, and it is the part that decides whether the output is useful.
This guide does not cover CI integration, self-hosted model gateways, or running Claude Code non-interactively in a pipeline.
Requirements
- A TypeScript repo that builds today. If
tsc --noEmitalready fails, fix that first — the agent will otherwise chase pre-existing errors. - Node.js 22.12 or newer (Claude Code ships as an npm package).
- Git, with a clean working tree. Every session should start from a commit you can return to.
Install Claude Code and open the repo
- Install the CLI globally.
- Change into your project root — not a subdirectory. Claude Code treats the directory you launch it from as the workspace boundary.
- Start a session and authenticate when prompted.
npm install -g @anthropic-ai/claude-code
cd ~/code/my-api
claudeConfirm the install resolved before you go further:
$ claude --version
2.0.14 (Claude Code)Write a CLAUDE.md that matches how the repo really builds
Inside the session, run /init. It scans the repo and writes a first-draft
CLAUDE.md at the root. That draft is a starting point, not the answer — it
tends to describe what a project usually looks like rather than what yours
does.
Rewrite it around commands you have personally run. The file is loaded into every session, so wrong information here is expensive.
# my-api
TypeScript 5.9, Node 22, Bun as the package manager and test runner.
Strict mode is on. `any` fails lint.
## Commands
- `bun install` — install dependencies
- `bun run typecheck` — `tsc --noEmit -p tsconfig.json`
- `bun test` — Vitest, run from the repo root
- `bun run lint` — ESLint, flat config in `eslint.config.ts`
## Layout
- `src/routes/` — Hono route handlers, one file per resource
- `src/db/` — Drizzle schema and queries; migrations are generated, never hand-edited
- `src/lib/` — shared helpers with no framework imports
## Rules
- Never edit `src/db/migrations/**` — regenerate with `bun run db:generate`
- Every new route needs a test in `src/routes/__tests__/`
- Do not add dependencies without askingThree properties make this file work: every command is copy-pasteable, every path is real, and the rules are things a reviewer would otherwise have to say out loud on a pull request.
Keep it short. Two hundred lines is already long; at that size the agent starts missing instructions buried in the middle.
Splitting CLAUDE.md across a monorepo
In a monorepo, put a short root CLAUDE.md describing the workspace layout and
a per-package CLAUDE.md in each app directory. Claude Code reads the file in
the current working directory and walks up toward the repo root, so the nearest
package file wins on specifics while the root file supplies shared conventions.
Restrict what the agent is allowed to run
By default every shell command stops and waits for approval. That is safe but slow, and approval fatigue leads people to hit “always allow” on things they should not.
Pre-approve the narrow set of commands you want running unattended, and deny the rest explicitly.
{
"permissions": {
"allow": [
"Bash(bun install)",
"Bash(bun run typecheck)",
"Bash(bun run lint)",
"Bash(bun test:*)",
"Bash(git status)",
"Bash(git diff:*)",
"Read(./src/**)",
"Edit(./src/**)"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Edit(./dist/**)",
"Bash(git push:*)",
"Bash(curl:*)"
]
}
}Commit that file. Personal overrides go in .claude/settings.local.json, which
should be in .gitignore.
The trailing :* is a prefix match: Bash(bun test:*) allows bun test,
bun test src/routes, and bun test --watch. Without it, only the exact
string matches. Run /permissions inside a session to see what is actually in
effect — that is faster than reasoning about rule precedence.
Give the agent a typecheck loop it can run itself
The single change that most improves output quality on a TypeScript repo is letting the agent see compiler errors without you relaying them.
A PostToolUse hook runs a command after every file edit:
{
"permissions": { "allow": ["Bash(bun run typecheck)"] },
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "bun run typecheck" }
]
}
]
}
}On a large codebase tsc --noEmit on every edit is too slow. Turn on
incremental builds so repeat runs are cheap:
{
"compilerOptions": {
"strict": true,
"incremental": true,
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo"
}
}Add node_modules/.cache to .gitignore if it is not already covered.
Work in slices small enough to review
Give one task per session and end it. A session that has run for two hours has accumulated context you cannot audit, and its later edits are worse than its early ones.
A loop that holds up:
git switch -c feat/rate-limit— branch before you start.- State the task in one sentence, including where the code should live.
- Let the agent write and typecheck.
- Read
git diffyourself. Not the agent’s summary of it — the diff. - Commit, then
/clearbefore the next task.
Step 4 is not optional. The agent’s summary of its own change is a description of intent, and intent and diff diverge quietly.
Use /clear rather than /compact between unrelated tasks. Compaction keeps a
lossy summary of the previous task, which shows up as the agent “remembering”
decisions you already reverted.
Common errors
claude: command not found after a successful global install. The npm
global bin directory is not on your PATH. Check where it went and add it:
npm config get prefix
# /home/you/.npm-global -> add /home/you/.npm-global/bin to PATHError: Cannot find module 'typescript' when the agent runs typecheck. It
launched the command from a subdirectory, or dependencies are not installed.
State the working directory rule in CLAUDE.md: all commands run from the repo
root.
The agent edits a file, then edits it back. Two instructions in CLAUDE.md
contradict each other, or a lint rule fights a formatting rule. Run
bun run lint and your formatter manually — if they disagree with each other,
the agent is stuck in the same loop you are.
Claude requested permissions to use Bash, but you haven't granted it yet.
The command does not match an allow rule. Add the prefix form rather than
approving one-off — repeated approval prompts are how overly broad rules get
added later.
Typecheck passes but the build fails. tsc --noEmit and your bundler do
not always resolve the same way, particularly with paths aliases. Add the
real build command to CLAUDE.md so the agent runs it before claiming done.
Security and production notes
Treat agent-authored commits as untrusted input until reviewed. Branch protection and required review apply exactly as they do for human commits.
Deny network commands (curl, wget, package publish) unless a task genuinely
needs them. A denied rule that occasionally costs you an approval prompt is
cheaper than an exfiltration path.
Never grant Bash(git push:*). Pushing is the one action that leaves your
machine, and it takes two seconds to do yourself.
Keep .claude/settings.local.json gitignored, and audit .claude/settings.json
in code review like any other config. A widened allow list is a security change.