Working with Claude Code: Tools and Permissions
Which tools Claude Code uses, how the permission system works, and when to give more or less freedom.
You give Claude Code the instruction to run a database migration. The model generates the SQL statement, opens a bash session, and executes npx prisma migrate dev. You saw the diff, clicked accept, and two seconds later Claude has modified your database schema. In production.
That didn’t happen — Claude Code’s permission system stopped it. But it could have happened if you don’t understand that system. Tools and permissions are the two mechanisms that determine what Claude Code can do and what it may do. The difference matters.
The toolbox
Claude Code works with a set of built-in tools. Each tool is a specific action the model can perform. The model chooses which tool to use based on your prompt. You see the tool calls in the output and can approve or reject them.
Read
Reads the contents of a file. This is the most-used tool — Claude reads files to gather context before doing anything.
Tool: Read
File: src/services/order.ts
Read is safe. It changes nothing. Claude uses it constantly and it doesn’t require your approval in the default permission mode.
Write
Creates a new file or overwrites an existing file with entirely new content.
Tool: Write
File: src/services/notification.ts
Content: [full file content]
Write always asks for approval in the default mode. You see the full content of the file before it’s written.
Edit
Modifies an existing file via targeted text replacements. Instead of overwriting the entire file, Edit specifies which lines change.
Tool: Edit
File: src/services/order.ts
Replace: [old text] → [new text]
Edit is more precise than Write. It leaves the rest of the file intact. You see a diff of the change. This is the tool Claude uses most often for existing code.
Bash
Executes a shell command in your terminal.
Tool: Bash
Command: npm test -- --grep "notification"
Bash is the most powerful and the most dangerous tool. Claude can use it to run tests, install dependencies, execute git commands, and — if you allow it — run destructive operations. The permission system pays the most attention to Bash.
Glob
Searches for files based on patterns. Similar to find or ls, but optimized for Claude Code.
Tool: Glob
Pattern: src/**/*.test.ts
Claude uses Glob to discover which files exist before reading them. Useful for exploring unfamiliar project structures.
Grep
Searches files for text patterns. Claude uses this to find functions, trace imports, and track down references.
Tool: Grep
Pattern: "updateOrderStatus"
Path: src/
Grep is essential to how Claude Code operates. The model can’t read everything at once. It searches specifically for relevant code and builds a picture of your project step by step.
Agent (sub-agents)
Claude Code can spawn sub-agents for parallel tasks. A sub-agent gets its own context and works independently on a subtask.
Tool: Agent
Task: "Find all places where OrderStatus is used and create an overview"
Sub-agents are useful for exploratory tasks: “find all TODO comments in the project,” “create an overview of all API endpoints,” “check which dependencies are outdated.” They run in parallel and report back.
The permission system
Claude Code has three permission levels. They determine how much freedom the model gets when executing tools.
Default: interactive approval
In the default mode, Claude Code asks permission for every write action. Read, Glob, and Grep run without approval. Write, Edit, and Bash pause and show what they intend to do. You evaluate and accept or reject.
This is the safest level. You see every change before it’s applied. The downside: with large refactorings involving dozens of file changes, you click “accept” a lot.
–permission-mode acceptEdits
claude --permission-mode acceptEdits
In this mode, Claude Code automatically accepts all Write and Edit operations. Bash commands still require approval. This is the middle tier: Claude can freely modify files, but doesn’t run commands without your permission.
Use this when you’re doing a refactoring where you know the file changes are safe, but you want control over what happens in the terminal.
–permission-mode bypassPermissions
claude --permission-mode bypassPermissions
Full freedom. Claude Code reads, writes, edits, and runs commands without asking permission. This is the mode for automated workflows — CI/CD pipelines, batch operations, or when you’re driving Claude Code from another system.
Use this only when you know what you’re doing. In this mode, Claude Code can run rm -rf, push commits, or install packages — all without asking your permission. The risks are real. The mode exists for automation, not for convenience.
Choosing the right mode
| Situation | Mode |
|---|---|
| Daily work, new features | Default |
| Large refactoring, many files | acceptEdits |
| Automated pipeline, CI/CD | bypassPermissions |
| Exploring an unknown project | Default |
| Production database in scope | Default, always |
The rule of thumb: start with default. Only switch when the repeated approvals slow down your workflow and you have sufficient confidence in what Claude is doing.
Hooks: automatic actions on tool calls
Hooks are scripts that run automatically when Claude Code uses a specific tool. They live in your .claude/ configuration and add a layer of automation to the permission system.
What hooks do
A hook reacts to a tool call. You can set up hooks that run before a tool call (pre-hook), after a tool call (post-hook), or as a notification on specific events.
Examples:
- Pre-hook on Bash: reject commands that contain
rm -rf. - Post-hook on Write: automatically run
prettieron every new file. - Post-hook on Edit: run the linter on the modified file.
Setting up a linter hook
Create a file .claude/settings.json (or .claude/settings.local.json for local settings) and add a hook:
{
"hooks": {
"postToolExecution": [
{
"matcher": {
"tool": ["write", "edit"],
"filePattern": "**/*.ts"
},
"command": "npx eslint --fix $CLAUDE_FILE_PATH"
}
]
}
}
Every time Claude Code writes or edits a TypeScript file, ESLint automatically runs over it. Errors that Claude introduces are corrected immediately — without you having to do it manually.
A pre-commit hook
A commonly used pattern: a hook that automatically runs tests after every change.
{
"hooks": {
"postToolExecution": [
{
"matcher": {
"tool": ["write", "edit"],
"filePattern": "src/**/*.ts"
},
"command": "npm test -- --bail --findRelatedTests $CLAUDE_FILE_PATH"
}
]
}
}
--findRelatedTests runs only the tests related to the modified file. --bail stops at the first failure. This gives you fast feedback without running the full test suite.
The result: Claude Code writes code, the hook automatically runs the relevant tests, and if a test fails Claude sees that in the output and can correct it immediately. The feedback loop gets shorter without you intervening.
Hooks and safety
Hooks are powerful, but they add complexity. A poorly configured hook can slow down every tool call, cause error messages that Claude misinterprets, or have unintended side effects.
Three rules for hooks:
-
Keep them fast. A hook that takes ten seconds after every edit makes your workflow unusable. Use
--bail,--findRelatedTests, and other flags that limit the scope. -
Keep them idempotent. A hook that runs twice should give the same result as a hook that runs once.
-
Test them separately. Run the hook command manually before putting it in the configuration. Verify that it works with the variables Claude Code passes (
$CLAUDE_FILE_PATH,$CLAUDE_TOOL_NAME).
The balance between freedom and control
Claude Code’s permission system is a design decision about trust. By default, the system trusts the model little: every write action requires approval. As your trust grows — in the model, in your project setup, in your hooks — you can grant more freedom.
That progression is deliberate. A developer who’s just starting with Claude Code wants to see every change. A developer who has been working with Claude Code for three months, has a robust test suite, and has set up hooks that automatically handle linting and testing, can use acceptEdits without significant risk.
The point isn’t to get to bypassPermissions as fast as possible. The point is to make the layer of automatic checks so good that the permission level matters less. A good test suite catches errors you’d miss in a diff. A good linter catches style errors you don’t want to review. Hooks automate what you’d do manually every time anyway.
Build the safety nets first. Expand the permissions after.
In part 4 we’ll look at MCP — the Model Context Protocol that connects Claude Code to external tools and data sources.
This is part 3 of the series “Working with Claude Code”. Read parts 1 and 2 for installation and steering.
Vic Boomer is an essay-led AI studio that turns ideas about AI, agents and software into clear analysis, working systems and practical tools.