ASK KNOX
beta
LESSON 383

The Harness Is Yours to Shape

Claude Code's behavior is configurable at five layers — settings.json, hooks, skills, slash commands, and CLAUDE.md — and every layer you customize is a decision that fires automatically, every session, without touching your token budget.

7 min read·Skills, Hooks & Slash Commands

Introduction

Most Claude Code operators spend 90% of their time perfecting their CLAUDE.md and nearly zero time on settings.json. That's backwards. Settings.json is the layer that enforces rules mechanically — it fires on every session, before Claude has read a single word of context, without consuming a token of your window budget.

The Five Customization Layers

Before building anything, understand what each layer actually does:

settings.json is the mechanical enforcement layer. It tells Claude Code which tools are allowed, which are blocked, how to route tasks to models, and what environment variables to inject. It operates before any context is loaded. A rule in settings.json cannot be overridden by Claude's reasoning or by instructions in CLAUDE.md.

Hooks are event-driven scripts that fire at specific points in the agent loop. They can block tool calls (exit 2, with the reason on stderr), warn about them (other non-zero exits surface a non-blocking error), or log side effects (exit 0 with output). Hooks are where you implement "if Claude ever tries X, stop it."

Skills (<name>/SKILL.md directories in ~/.claude/skills/) are load-on-demand workflow units. They encapsulate multi-step expert processes — a 10-step deployment pipeline, a code audit protocol, a content generation workflow. Skill bodies load only when invoked, so 60 skills cost you almost nothing when you're doing something unrelated.

Slash commands (.md files in .claude/commands/ or ~/.claude/commands/) are versioned prompt shortcuts — a prompt you've already debugged, saved under a one-word invocation like /pr-description. They're the lightest layer: no logic, just a proven prompt you reuse exactly. The next lesson covers them in full.

CLAUDE.md is the semantic instruction stack. It's where you explain architecture, conventions, project context, and rules that require Claude to understand nuance. It's the only layer that can encode "this project uses Supabase for auth" because that's not a mechanical rule — it's domain knowledge.

The Three settings.json Files

The settings hierarchy has three levels, each with a distinct purpose:

~/.claude/settings.json is your global configuration — it applies to every project on this machine. This is where you put machine-specific model preferences, universal tool permissions, and cross-project deny patterns.

.claude/settings.json (project level, checked into git) contains project-specific permissions and hooks. This file ships with the repo, so every developer working on the project gets the same enforcement. Knox's academy repo has hooks that enforce 90% test coverage before any session ends.

.claude/settings.local.json (gitignored) is for local overrides. Useful for developer-personal additions that don't belong in the team's shared config — a personal model preference, a local secret, or a permission that's correct for your machine but not the CI environment.

Priority order for settings values: local overrides project overrides global. A model preference in your local file beats the project's, which beats your global default.

Permission rules work differently. Allow and deny patterns from all three files are merged into one rule set, and deny rules always take precedence over allow rules — regardless of which file they live in. A deny in the project file cannot be defeated by an allow in your local file. File precedence decides which value wins when the same setting appears twice; rule evaluation order (deny beats ask beats allow) decides what happens when permission patterns conflict. Keeping those two mechanisms straight is what makes the deny list a reliable safety floor.

The Decision: Config vs. Prompt

The most common mistake is putting mechanical rules in CLAUDE.md when they belong in settings.json, or trying to encode semantic context in settings.json where it has no meaning.

The test is simple: if violating the rule once causes immediate damage, it belongs in config. "Never commit to main" is Tier 1 CLAUDE.md content AND a settings.json hook — the hook is the enforcer, the CLAUDE.md entry is the explanation.

If the rule requires Claude to understand context — "this project uses Supabase, not Firebase" — it belongs in CLAUDE.md because settings.json has no mechanism to encode architectural knowledge.

Why Config Beats Prompting

Consider Knox's anti-main-branch hook: a bash script that runs on every git push Bash command, checks whether it targets main, and exits 2 if it does. The stderr message tells Claude exactly why the command was blocked and what to do instead (create a feature branch).

Without this hook, Claude Code might push to main if:

  • The CLAUDE.md is too long and the rule gets buried
  • A new session starts before CLAUDE.md is fully loaded
  • A complex task creates enough distraction that the instruction is overlooked

The hook fires regardless. It costs zero tokens. It does not require Claude to have read anything. It is not subject to context pressure or instruction priority conflicts.

Building Your First Customization

The fastest way to feel the difference is to add one meaningful entry to .claude/settings.json today. Start with a deny pattern for something you've been manually reviewing:

{
  "permissions": {
    "deny": ["Bash(git push --force:*)"]
  }
}

That one line is now enforced on every Bash call, every session, with zero token cost. It cannot be overridden by Claude's reasoning, other instructions, or context pressure. That is the power of the config layer.

Once you feel that, you're ready to build the full customization stack — hooks, skills, slash commands, and a CLAUDE.md hierarchy that stays clean because it only carries what CLAUDE.md can actually do.

What's Next

The next lesson introduces slash commands — the saved-prompt mechanism that turns your most-used workflows into one-word invocations. We'll cover project vs. personal scope, the $ARGUMENTS pattern, and when a command beats a chat turn.