chenglou/claude-config-learnings: The Shell Hooks That Turn Claude Code Into a Permission Firewall

A tiny Bash toolkit shows how Claude Code’s hook layer can override rigid permissions, repair dontAsk mode, and make safety decisions from live context.

8 min read • View on GitHub • More from chenglou

A fortified terminal checkpoint stands in the center of a white field, with three visible lanes marked deny, allow, and ask. A hidden side hatch beneath the floor feeds into the checkpoint, showing that the real control point lives below the visible policy surface. The image explains that Claude Code hooks can act like a programmable firewall rather than a static config file.
The visible permission menu is only half the story. Hooks can sit underneath it and decide what happens next.
Key Takeaways

The control layer Claude Code does not advertise

The interesting part of this repo is not the scripts themselves. It is the discovery that Claude Code’s permission behavior is not just a settings file with three moods. The hooks, especially PreToolUse, can sit in front of tool execution and make a live decision from stdin, which means enforcement is programmable.

That changes the mental model. settings.json sets defaults, but hooks can act like policy middleware. In this repo, the hook is not decoration. It is the place where permission can be denied, forced through, or sent back for another look.

The one-line fix for non-interactive mode

The sharpest proof is the smallest file: allow-all.sh. It returns a hardcoded JSON response that says the current PreToolUse event should be allowed. That sounds trivial until you connect it to the reported dontAsk bug, where non-interactive mode can behave like an overzealous gate instead of a hands-off assistant.

echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'

That one line matters because it restores the intended direction of travel. A non-interactive session should not require a human to click through obvious safe actions. The repo treats that as a control-plane bug, not a UX preference.

A close-up of a Bash script page inside a terminal window, with one JSON object leaving stdout like a stamped permit. The scene focuses on the boundary between code and policy, where a single response can change whether Claude Code proceeds, pauses, or asks again. It explains how a shell hook can steer agent behavior with very little surface area.
The smallest possible hook can still control the boundary where tool calls are approved.

How the permission firewall makes decisions

The main idea in smart-permission.sh is a staged decision tree. Fast safe commands get out of the way immediately. Suspicious patterns get rejected or flagged. Ambiguous cases move up to a transcript-aware fallback, where the script pulls recent context from the conversation and asks a model to judge the request.

Permission is not a binary here. It is a staged negotiation between local rules, pattern checks, and context-aware escalation.

command=$(jq -r '.tool_input // empty')

if echo "$command" | grep -Eq '^(ls|pwd|git status)$'; then
  echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
elif echo "$command" | grep -Eq 'rm|git reset|git clean'; then
  echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny"}}'
else
  transcript=$(tail -c 50000 "$transcript_path")
  # ask the model whether this request is safe in context
fi

The mechanics are old school on purpose. JSON comes in on stdin. jq extracts the payload. grep handles blunt pattern checks. If the command is still ambiguous, the hook can read recent transcript context and make a more informed call.

That is the surprising part. The repo is not trying to replace Claude Code with another framework. It is showing that a shell script, a few Unix tools, and a model call can create a credible safety layer around an agent.

Why hooks beat settings.json

This is where the repo gets opinionated. settings.json is useful, but it is static. It can declare preference, not runtime policy. Hooks can inspect the actual request, the current context, and the state of the conversation before deciding what to do.

LayerWhat it doesWhat it cannot do
settings.jsonSets static permission defaults and broad behavior.Cannot express nuanced policy flows at runtime.
CLAUDE.mdGives the agent local instructions and project context.Cannot enforce execution-time safety on its own.
Hook-based firewallInspects live tool requests and returns allow, deny, or ask.Is more complex to maintain than a plain config file.

That difference matters in practice. A config file tells the agent what should usually happen. A hook decides what happens now. For agent workflows, that is the difference between guidance and enforcement.

What this repo says about agent workflows

The broader lesson is bigger than Claude Code. If an agent exposes a hook boundary, you can build policy around it with the tools Unix already gave you. Shell remains relevant because it is simple, inspectable, and close to the metal.

That also explains the repo’s style. It reads like a lab notebook for control flow, not a productized toolkit. The value is in the pattern: lightweight interception, explicit branching, and a fallback path that uses context instead of blind rules.

In the landscape around Claude Code, the contrast is clear. Team CLAUDE.md files guide behavior. Centralized harnesses try to orchestrate more of the agent. This repo sits in between, using hooks as a narrow but powerful control plane.