Lesson 04 of 14 · Modules
Claude Code skills & hooks
Make an agentic coding tool repeatable and safe for infrastructure work, using Claude Code as the example: project memory in CLAUDE.md, permission rules, skills that package procedures, hooks that enforce guard rails deterministically, and non-interactive use in CI.
Why configure an agent at all?
Out of the box, an agentic tool is a smart generalist. For infrastructure work you want it to know your conventions, follow your procedures, and never do certain things. Claude Code (Anthropic's agentic coding tool for the terminal and IDEs) provides several layers for this. Other tools have similar concepts; check your tool's documentation for exact names and formats, which evolve quickly.
Think of a new helper in a workshop. The notice board tells them how things are done here (CLAUDE.md). Recipe cards explain specific jobs step by step (skills). The locks on dangerous machines stop anyone using them without a supervisor, no matter what the notice board says (permissions and hooks).
Layer 1: project memory (CLAUDE.md)
# Platform repo: notes for Claude
- Terraform lives in `infra/`, one root module per environment in `infra/live/<env>`.
- Run `make lint test` before proposing changes; never run `terraform apply`.
- Kubernetes manifests: Kustomize, base + overlays; validate with `make kubeconform`.
- Style: Bash with `set -euo pipefail`; Python formatted with ruff.
- Never print or commit secrets; secrets come from External Secrets.
Keep it short and factual; it's loaded into context for every session in that repo.
Layer 2: permissions
{
"permissions": {
"allow": [
"Bash(make lint:*)",
"Bash(make test:*)",
"Bash(kubectl get:*)",
"Bash(kubectl describe:*)",
"Bash(terraform plan:*)"
],
"deny": [
"Bash(kubectl delete:*)",
"Bash(terraform apply:*)",
"Bash(terraform destroy:*)",
"Read(./.env)",
"Read(./secrets/**)"
]
}
}
Everything not allowed asks for approval. (Rule syntax is documented in the Claude Code settings reference; keep rules narrow.) Also run the agent with credentials that can't do damage: a read-only kubeconfig context, and no production cloud credentials in the shell.
Layer 3: skills
A skill packages a procedure:
.claude/skills/k8s-upgrade-check/
├── SKILL.md
└── check.sh
---
name: k8s-upgrade-check
description: Check Helm charts and manifests for Kubernetes API deprecations and removals before a cluster upgrade. Use when asked to assess upgrade readiness.
---
1. Ask for (or detect) the target Kubernetes version.
2. Render charts with `helm template` for each environment's values.
3. Run `./check.sh <rendered-dir> <target-version>` (wraps pluto/kubent).
4. Report: removed APIs (blocking), deprecated APIs (warning), with file paths and the replacement apiVersion.
5. Do not modify files unless asked; propose changes as a diff.
The deterministic tool (pluto/kubent) finds the facts; the agent orchestrates and explains.
Layer 4: hooks
Hooks run your code at points in the agent's lifecycle. A PreToolUse hook can inspect a pending shell command and block it:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [ { "type": "command", "command": "python3 .claude/hooks/guard.py" } ]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [ { "type": "command", "command": "make fmt >/dev/null 2>&1 || true" } ]
}
]
}
}
# .claude/hooks/guard.py: block commands that target production contexts
import json, re, sys
event = json.load(sys.stdin)
cmd = event.get("tool_input", {}).get("command", "")
if re.search(r"--context[= ]\S*prod|kubectl\s+(delete|drain|cordon)", cmd):
print("Blocked by guard hook: production context or destructive kubectl verb.", file=sys.stderr)
sys.exit(2) # exit code 2 = block; stderr is shown to the agent
sys.exit(0)
Hooks are code with your permissions: review them like any script, and keep them fast.
Subagents and headless runs
- Subagents (
.claude/agents/*.md) give focused roles (e.g. "terraform-reviewer") their own instructions and restricted tools. - Headless mode (
claude -p "…") runs non-interactively, e.g. in CI for PR review (lesson 06) or scheduled reports, with the same permissions and hooks applied.
Try it: a guarded infra assistant
- In a sandbox repo with some Kubernetes manifests, write a short CLAUDE.md with conventions and commands.
- Add permissions: allow
kubectl get/describeand your lint command; denykubectl deleteandterraform apply. - Add the guard hook above, then ask the agent to delete a pod in a context named
prod-…and confirm it's blocked. - Create the
k8s-upgrade-checkskill (use pluto or kubent incheck.sh) and run it on a chart with an old apiVersion. - Commit
.claude/to the repo so the whole team gets the same setup; review it like code.
Going deeper: team-wide agent setup
- Keep shared settings in the repo and personal overrides local; review changes to hooks and permissions carefully.
- Build a small library of skills for recurring infra tasks (upgrade checks, runbook drafting, incident timeline from logs).
- Log agent sessions and tool calls where your policy requires auditability (lesson 14).
Recap
- CLAUDE.md: conventions and commands (guidance).
- Permissions: allow safe, deny dangerous, ask for the rest; plus low-privilege credentials.
- Skills: packaged, reviewable procedures that orchestrate deterministic tools.
- Hooks: deterministic guard rails (PreToolUse can block with exit code 2) and automation (PostToolUse).
- Subagents and headless mode for focused roles and CI.
This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.