Skip to content
HN On Hacker News ↗

I Read the Claude Code Source Code. Here's Everything You Can Configure That the Docs Don't Tell You.

▲ 326 points 65 comments by ankitg12 4w ago HN discussion ↗

Pangram verdict · v3.3

We believe that this document is fully AI-generated

98 %

AI likelihood · overall

AI
0% human-written 100% AI-generated
SEGMENTS · HUMAN 0 of 6
SEGMENTS · AI 6 of 6
WORD COUNT 1,513
PEAK AI % 99% · §1
Analyzed
May 29
backend: pangram/v3.3
Segments scanned
6 windows
avg 252 words each
Distribution
0 / 100%
human / AI fraction
Verdict
AI
Pangram v3.3

Article text · 1,513 words · 6 segments analyzed

Human AI-generated
§1 AI · 99%

Claude Code’s auto-mode permission system is internally called the “YOLO Classifier.” That’s the actual variable name in yoloClassifier.ts. And you can configure it with plain English descriptions of your environment, things like “this is a staging server, destructive operations are acceptable,” that the classifier reads to decide what’s safe to auto-approve. This isn’t in any documentation.It’s one of dozens of undocumented capabilities buried in the Claude Code source code, which is sitting right there in your node_modules as a publicly distributed npm package. The official docs cover the basics well enough. But the source code reveals fields, response formats, and settings that dramatically expand what you can build. Everything here works right now, and every example is designed to be dropped into your project as-is.A note on versioning: These findings come from @anthropic-ai/claude-code@2.1.87. Undocumented features can change between releases, so treat this as a snapshot of what’s available today. Fields with “EXPERIMENTAL” in their names are explicitly flagged as unstable by Anthropic’s own engineers, and I’ll call those out individually.Quick reference for where everything lives:Settings: ~/.claude/settings.json (personal) or .claude/settings.json (project, shared via git)Skills: ~/.claude/skills/<name>/SKILL.md (personal) or .claude/skills/<name>/SKILL.md (project)Agents: ~/.claude/agents/<name>.md (personal) or .claude/agents/<name>.md (project)Hook scripts: ~/.claude/hooks/ is a good convention. Remember to chmod +x your scripts.Project-level files in .claude/ can be committed to git and shared with your team. Personal files in ~/.claude/ are yours alone.This is the biggest gap in the documentation. The docs tell you hooks receive JSON on stdin and that exit code 2 blocks an operation. What they don’t tell you is that hooks can return JSON on stdout with event-specific fields that modify Claude Code’s behavior in real time. The source code reveals exactly what each event type accepts.PreToolUse hooks can return:updatedInput - rewrite the tool’s input before it executes.

§2 AI · 98%

You can modify commands mid-flight.permissionDecision - force “allow” or “deny” without prompting the user.permissionDecisionReason - explain the decision (shown in UI).additionalContext - inject text into the conversation context.SessionStart hooks can return:watchPaths - set up automatic file watching that triggers FileChanged events.initialUserMessage - prepend content to the first user message in the session.additionalContext - inject context that persists for the whole session.PostToolUse hooks can return:updatedMCPToolOutput - modify what Claude sees from an MCP tool response.additionalContext - inject context after a tool runs.PermissionRequest hooks can return:decision - programmatically allow or deny with updatedInput or updatedPermissions.This is powerful stuff. Here’s a PreToolUse hook that automatically adds --dry-run to any git push command before Claude executes it.In your settings.json:{ "hooks": { "PreToolUse": [{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "~/.claude/hooks/dry-run-pushes.sh" }] }] } } And the script at ~/.claude/hooks/dry-run-pushes.sh:#!/bin/bash INPUT=$(jq -r '.tool_input.command' < /dev/stdin) if echo "$INPUT" | grep -q 'git push'; then jq -n --arg cmd "$INPUT --dry-run" '{"updatedInput": {"command": $cmd}}' fi Claude thinks it’s running git push origin main, but your hook quietly rewrites it to git push origin main --dry-run before execution. The updatedInput field isn’t in any docs.Here’s a SessionStart hook that watches your config files and injects git context into every session.settings.json:{ "hooks": { "SessionStart": [{ "hooks": [{ "type": "command", "command": "~/.claude/hooks/session-context.sh", "statusMessage": "Loading project context..." }] }] } }

§3 AI · 97%

~/.claude/hooks/session-context.sh:#!/bin/bash BRANCH=$(git branch --show-current 2>/dev/null) CHANGES=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')

jq -n \ --arg branch "$BRANCH" \ --arg changes "$CHANGES" \ '{ "watchPaths": ["package.json", ".env", "tsconfig.json"], "additionalContext": "Current branch: \($branch). Uncommitted changes: \($changes) files." }' Now Claude Code automatically watches your package.json, .env, and tsconfig for changes, and it knows what branch you’re on and how many uncommitted files you have before you even type anything.And here’s one that auto-approves read-only bash commands without prompting.settings.json:{ "hooks": { "PreToolUse": [{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "~/.claude/hooks/auto-approve-readonly.sh" }] }] } } ~/.claude/hooks/auto-approve-readonly.sh:#!/bin/bash CMD=$(jq -r '.tool_input.command' < /dev/stdin) if echo "$CMD" | grep -qE '^(ls|cat|echo|pwd|whoami|date|git status|git log|git diff)'; then echo '{"permissionDecision": "allow", "permissionDecisionReason": "Safe read-only command"}' fi You’re basically building your own permission classifier with shell scripts. The permissionDecision field isn’t in any docs.The documented hook fields are type, command, matcher, timeout, if, and statusMessage. The source code parser accepts three more that fundamentally change how hooks behave.once: true fires the hook exactly once, then auto-removes it. Perfect for first-session setup:{ "hooks": { "SessionStart": [{ "hooks": [{ "type": "command", "command": "[ -f .env ] || cp .env.example .env && echo 'Created .env from template'", "once": true, "statusMessage": "First-time setup..." }] }] } } Simple enough to inline.

§4 AI · 94%

It checks if .env exists, copies the template if not, and never runs again.async: true runs the hook in the background without blocking Claude. Fire and forget:{ "hooks": { "PostToolUse": [{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "jq '{timestamp: now, command: .tool_input.command, session: .session_id}' < /dev/stdin >> ~/.claude/audit.jsonl", "async": true }] }] } } That logs every bash command to an audit file without adding any latency to your session.asyncRewake: true is the clever one. It runs in the background like async, so it doesn’t block on the happy path. But if it exits with code 2, it wakes the model back up and blocks the operation. Non-blocking when everything’s fine, blocking when something’s wrong:settings.json:{ "hooks": { "PostToolUse": [{ "matcher": "Write|Edit", "hooks": [{ "type": "command", "command": "~/.claude/hooks/scan-secrets.sh", "asyncRewake": true, "statusMessage": "Scanning for secrets..." }] }] } } ~/.claude/hooks/scan-secrets.sh:#!/bin/bash FILE=$(jq -r '.tool_input.file_path // .tool_response.filePath' < /dev/stdin) if grep -qE '(password|secret|api_key)\s*=' "$FILE" 2>/dev/null; then exit 2 # Block: secrets detected fi exit 0 # Clean: carry on This scans every file Claude writes for hardcoded secrets. If it finds one, it blocks and tells Claude. If not, you never even notice it ran.The documentation covers name, description, allowed-tools, argument-hint, when_to_use, and context. The actual frontmatter parser in the source code accepts six more.model lets you override which model runs the skill.

§5 AI · 96%

Use haiku for cheap, fast tasks and opus for complex analysis:--- name: quick-lint description: Fast lint check using the cheapest model model: haiku effort: low allowed-tools: Bash, Read argument-hint: "[file]" --- Run the project linter on: $ARGUMENTS Detect the linter from config (eslint, ruff, clippy) and run it. Report only errors, not warnings. That runs on Haiku at low effort, so it’s fast and cheap. For a deep architecture review you’d want model: opus and effort: max.effort controls how hard the model thinks. low, medium, high, or max. This maps to the same effort system that internally controls reasoning depth per response.hooks defines hooks scoped to when the skill is active. They register when the skill fires and deregister when it completes:--- name: strict-typescript description: Write TypeScript with type checking on every save allowed-tools: Bash, Read, Write, Edit, Grep, Glob hooks: PostToolUse: - matcher: "Write|Edit" hooks: - type: command command: "~/.claude/hooks/typecheck-on-save.sh" statusMessage: "Type checking..." - type: command command: "~/.claude/hooks/lint-on-save.sh" async: true --- Write TypeScript with strict enforcement. Every file you touch gets type-checked and linted automatically. $ARGUMENTS ~/.claude/hooks/typecheck-on-save.sh:#!/bin/bash FILE=$(jq -r '.tool_input.file_path // .tool_response.filePath' < /dev/stdin) [[ "$FILE" == *.ts ]] && npx tsc --noEmit 2>&1 || true ~/.claude/hooks/lint-on-save.sh:#!/bin/bash FILE=$(jq -r '.tool_input.file_path // .tool_response.filePath' < /dev/stdin) [[ "$FILE" == *.ts ]] && npx eslint --fix "$FILE" 2>&1 || true While this skill is running, every TypeScript file Claude writes gets type-checked synchronously and linted in the background. When the skill finishes, those hooks disappear.

§6 AI · 99%

The scoping is clean.agent delegates the skill to a custom agent:--- name: deep-review description: Thorough security review delegated to the review agent agent: security-review --- Review the following: $ARGUMENTS disable-model-invocation: true prevents auto-invocation. Only explicit /skill-name works. Use this for destructive skills you don’t want firing accidentally.shell: bash specifies which shell to use for execution.Custom agents in .claude/agents/ support frontmatter fields the documentation doesn’t mention.color sets the UI color: red, orange, yellow, green, blue, purple, pink, or gray. Helps visually distinguish agents when multiple are running.memory is the big one. It gives the agent persistent memory across invocations:user - global, persists across all projectsproject - per-project persistencelocal - private per-project (gitignored)This means you can build an agent that learns. A security reviewer that tracks past findings. A code reviewer that remembers your patterns across sessions. The memory uses the same frontmatter format as the auto-memory system.--- name: codebase-guide description: Answer questions about the codebase, learning more with each session tools: [Read, Grep, Glob, Bash] color: green memory: project --- You are a codebase guide with persistent memory. Check your memory first before exploring the code.

After answering a question, save useful context to memory: - Architecture decisions (type: project) - Code locations for common tasks (type: reference) - Patterns and conventions (type: feedback)

Over time, you should answer faster because you remember where things are. After a few sessions, this agent builds a knowledge base about your codebase and starts answering from memory before grepping.omitClaudeMd: true skips loading the CLAUDE.md instruction hierarchy. Useful for a “fresh eyes” reviewer that applies industry standards rather than your project’s conventions:--- name: fresh-eyes description: Review code without project-specific biases tools: [Read, Grep, Glob] omitClaudeMd: true effort: high color: blue --- Review this code purely from first principles. You have no project context. Focus on correctness, security, performance, and readability by industry standards. criticalSystemReminder_EXPERIMENTAL is a short message re-injected at every turn as a system reminder.