cd /news/ai-agents/my-claude-code-setup-guardrails-a-pl… · home › topics › ai-agents › article
[ARTICLE · art-156116] src=gist.github.com ↗ pub= topic=ai-agents verified=true sentiment=↑ positive

My Claude Code setup: guardrails, a plan gate, shared memory, a self-maintaining wiki that dreams, and the toolbox around it

Developer Andreas Heige published an open-source toolkit (andreasheige/claude-plugins, MIT) for running Claude Code safely in day-to-day work, combining a PreToolUse guardrail hook that blocks destructive commands and credential reads, a plan-gate Stop hook that keeps the agent working through a plan, shared markdown memory readable by Claude Code, Codex CLI and Copilot CLI, and nightly headless 'dreaming' jobs that propose new memory facts from past sessions. Heige recommends starting with a single repo and a small core of CLAUDE.md, a deny list, the guardrail with its canary, and one proof-of-done command, adding memory, wiki and graph layers only when a concrete annoyance demands them.

by read16 min views3 publishedOct 21, 2026

This is a tour of how I run Claude Code day to day: the ideas, how the pieces connect and what broke along the way. Steal whatever is useful.

Want to install the core instead of reading about it? The guardrails, the plan gate, the workflow plugin and starter files (CLAUDE.md, deny list, git hooks, a plan, a rule, a memory fact) are in andreasheige/claude-plugins (MIT), with a step-by-step "start here".

Don't install all of this. The order I'd recommend:

  1. One repo, one recurring task. Something you do every week, not a greenfield demo.
  2. A small core: a short CLAUDE.md, a deny list, the guardrail with its canary (section 1), and one command that proves the work is done (tests, a build, a real run).
  3. Plans with a check per step, and/plan-gate so the agent finishes them (section 2).
  4. A skill only when the same pain shows up a second time. Not before.
  5. Prune. Every line of CLAUDE.md and every skill description is paid for on every turn.

Memory, the wiki, the graph and time logging (sections 3–6) are add-ons. Add one when a concrete annoyance asks for it.

Layer What it does Pieces
1. Guardrails Stops the agent from doing irreversible or credential-leaking things guardrail.sh PreToolUse hook, a secret-placement hook shared with other agents, a canary and drift check at session start, global git hooks
2. Workflow Keeps the agent working through a plan, and keeps the implementer and tester separate plan-gate Stop hook, a dual-loop dev workflow plugin
3. Memory Durable facts that Claude Code, Codex CLI and Copilot CLI all read and write Plain markdown files in an Obsidian vault
4. Wiki + dreaming A self-maintained wiki, plus nightly "dreaming" that proposes new memory facts from my own sessions Headless claude -p jobs on launchd
5. Code knowledge A knowledge graph per repo and a global graph across repos graphify plus a small fleet wrapper
6. Time The agent logs its own time per phase of work tt with hooks
7. Toolbox Knowledge and rituals loaded only when needed Skills, slash commands, a personal plugin marketplace, MCP servers
flowchart LR
  S[Claude / Codex / Copilot sessions] -->|commits, activity| A[Autograph capture]
  S -->|transcripts| X[dream-extract<br/>scrub secrets, digests]
  X --> P[propose facts<br/>Sonnet, evidence quotes]
  P --> J[judge facts<br/>Opus, fresh context]
  J -->|ticked, next day| G{evidence gate}
  G -->|quote found| M[(shared memory<br/>markdown + git)]
  A --> W[wiki ingest]
  M --> W
  W --> I[(wiki: concepts,<br/>entities, sources)]
  M -->|read at session start| S
  I -->|/wiki-query| S

permissions.deny in settings.json is the first line of defence. A PreToolUse hook on Bash|Read is the second. It covers things the permission patterns can't express well:

  • Destructive commands:sudo ,rm -rf (it suggeststrash ),mkfs ,dd
  • Git:git merge (I only rebase), plain--force ,reset --hard , and pushes to main/master
  • Infrastructure:terraform apply/destroy andaz … delete
  • Credentials: reading~/.ssh ,~/.aws ,.netrc , Keychains or.env files, through either Bash or the Read tool
deny() {
  jq -n --arg reason "$1" \
    '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$reason},systemMessage:$reason}'
  exit 0
}

if echo "$cmd" | grep -qE '\bgit\s+merge\b' && \
   ! echo "$cmd" | grep -qE '\bgit\s+merge\s+--(abort|continue|quit)\b'; then
  deny "git merge is blocked (rebase-only policy). Use 'git fetch && git rebase origin/main' instead."
fi

The deny reason tells the model what to do instead, so a block steers it rather than leaving it stuck.

The guard on the guard. The hook fails open: if jq is missing or the script errors, it allows the command. A broken guardrail is therefore silent. A SessionStart hook sends a canary payload through it and warns loudly if the payload isn't denied.

Drift guard. The same SessionStart check warns when something rewrote my settings behind my back: a hook script that no longer exists, a changed status line, deny rules that crept into settings.local.json. Installers for other tools do this without asking. The check only warns; it never fixes.

Shared with other agents. A second PreToolUse hook blocks secrets written into shell rc files (real values belong in an untracked ~/.zshrc.local). It lives outside ~/.claude, so Codex runs the same script, and Copilot runs it as an audit after the write.

Know its ceiling. These are string matches. They stop the common accidents and steer the agent to a safer command, but an obfuscated command gets through, and harmless ones get blocked: my own guardrail refused a Python script today because a comment in it said "git merge". It adds friction, not containment. For real isolation, use a sandbox or container.

Claude's hooks only see Claude. A global core.hooksPath applies the same policy to every commit on the machine, whoever or whatever made it:

  • commit-msg enforces Conventional Commits.
  • pre-merge-commit rejects merge commits (rebase only).
  • pre-commit refuses a credential in a dotfile or.env , by calling the same secret scanner the agent hook uses, so the patterns live in one place. This is what catches Copilot, whose hook can only audit after the write.

Gotcha: once core.hooksPath is set, git stops running each repo's own .git/hooks/. Every global hook therefore also runs the repo's hook of the same name.

My whole config directory is a private repo with a deny-by-default .gitignore:

*
!.gitignore
!CLAUDE.md
!settings.json
!hooks/
!hooks/**
!skills/
!skills/**
!commands/
!commands/**

settings.local.json stays untracked. Every change to a hook, skill or principle is now a reviewable diff, and a bad change is one git revert away.

Agents love to stop and ask "should I continue?". /plan-gate plans/foo.md arms a Stop hook that reads the plan:

if (/^\s*BLOCKED:/m.test(planText)) allow();
// otherwise, while "- [ ]" or "- [~]" items remain:
JSON.stringify({ decision: "block", reason:
  `The plan ${plan} still has ${items.length} open item(s). Next: "${items[0]}". ` +
  `Keep going: do it, verify it, tick it (- [x]). ` +
  `Only if you need the user (...) add a line "BLOCKED: <reason>" to the plan, then stop.` })
  • No stored state. The hook finds the latest/plan-gate command in the transcript, so/clear or a new session resets it.
  • When to stop.BLOCKED: is reserved for real ambiguity, ask-first areas (auth, payments, PII, migrations, infra) and outward actions (push, PR).
  • Safety limits. At most 10 forced continuations per prompt, and it fails open on errors.

What a gate-able plan looks like (full example):

Goal: … Non-goals: …
## Design
## Open decisions
## Steps
- [x] 1. Read the existing middleware chain and config 
      → verify: list the files touched by step 2 in this plan.
- [~] @agent-1 2. Add the limiter middleware with config-driven limits
      → verify: unit test — 61st anonymous request in a window returns 429.
- [ ] 3. Run the real server and hit it with `hey -n 100`
      → verify: observed 429 count matches the limit (paste the output).
## Review

Every step carries its own → verify:. A plan without checks gets "done" items that were never proven. Non-goals keep the agent from wandering. [~] @agent-1 marks who is working on what when several agents share one plan.

Cheap habits that cut cost more than any tool did:

  • /clear at every task boundary. Everything in context is re-sent on every turn, so a long session pays again and again for stale history. Clearing early beats waiting for auto-compaction, which costs a summarisation pass on top.
  • Low effort for routine work (mechanical edits, config, log triage). Save full thinking for design and review.
  • /context early in a big session, to see what CLAUDE.md and MCP tool definitions already cost before real work starts.
  • Record the quiet flag once. The first time a noisy test or build command runs in a repo, add it with its quiet flag to that repo's CLAUDE.md, so the discovery cost is paid once.

A personal plugin encodes the full development flow:

  • Outer loop: PRD → architecture → slice the epic into tickets.
  • Inner loop, per ticket: prime only the relevant files → atomic plan with a check per step → implement → validate → fresh-context review → rebase and land.

What makes it work:

  • An adversarial tester that never tests its own session's code. It reads only the diff and the requirements, not the implementer's reasoning. It checks each test by breaking the code in exactly the way the test claims to catch."Success is the Implementer's code surviving your adversarial suite — not your suite passing easily."
  • Five validation levels. Level 4, actually running the code and looking at real output, can never be skipped, not even for "obviously correct" one-liners.
  • The architecture reviewer always runs in a new context, never in the session that wrote the code.
4. Goal-driven execution. Every non-trivial step should be expressible as
   `[step] → verify: [check]`. If you can't state the check, you don't
   understand the step yet.
5. Undo when adrift. If a change goes sideways, revert to the last known-good
   checkpoint rather than layering corrective prompts on a confused context.
6. Iterate on rules. If you correct the same mistake more than once in a
   session, fix the standing rule or skill, not just the in-session symptom.

Each agent tool has its own memory, so a fact Claude learns never reaches Codex. My fix is one folder of plain markdown in the vault:

vault/
  Memory.md                     # format + rules
  personal/memory/memory.md     # index, one line per fact
  personal/memory/<fact>.md     # one fact per file
  projects/<repo>/memory/...    # per-repo facts
---
name: terminal-is-warp
description: Never hard-code Terminal.app; read __CFBundleIdentifier
tags: [type/gotcha, tool/any]
metadata: { added_by: claude-code, date: 2026-10-20 }
---
<the fact>
**Why:** ...
**How to apply:** ...

Each tool finds it differently:

  • Claude Code: a section in the globalCLAUDE.md .
  • Codex:~/.codex/AGENTS.md is a symlink to a sharedAGENTS.md .
  • Copilot CLI:COPILOT_CUSTOM_INSTRUCTIONS_DIRS points at the same folder.

Index first, facts on demand. Only the index (memory.md, one line per fact) is read at session start, and it stays small (a few KB). A fact file is opened only when its line matches the task. every fact every session would cost tens of KB of context per turn and drown the relevant ones. (template)

Because it's plain files in git, I can read, diff and revert anything an agent wrote.

The wiki follows Karpathy's LLM-wiki pattern with a layout Obsidian can read:

  • Pages:sources/ ,entities/ ,concepts/ ,answers/ , anindex.md and an append-onlylog.md .
  • Origin tag: every page carries anorigin: tag (dev | work | personal ). Work-derived pages never leave the wiki folder.
  • Schedule: a daily launchd job runs headless ingest in batches.
  • Lint: a weekly lint pass runs on a local Ollama model.

Every morning:

  1. Extract. Take only what I typed and the assistant's text replies from the last 3 days of transcripts. Skip sessions still active in the last hour, scrub secrets and emails, cap each message and write digests.
  2. Propose (Sonnet, $2 budget). At most 10 candidate facts, each a gotcha, decision, convention or preference, each withevidence: <digest> "<verbatim quote>" .
  3. Judge (Opus, $3 budget, fresh context). A skeptical reviewer keeps a candidate only if it is supported, useful, durable, new and clean. It may tighten the privacy origin but never loosen it.
  4. Wait a day. Ticked candidates are applied on thenext run, so I have a veto window. A file with nothing ticked expires after 7 days.
  5. Evidence gate. At apply time, every part of the quote (8 or more characters each) must occur in the cited digest after normalisation. If not, the candidate is skipped. Existing facts are never overwritten.

The model can't write a fact that isn't backed by something actually said in a session.

claude -p --model "$MODEL" "$prompt" \
  --setting-sources project --strict-mcp-config \
  --permission-mode dontAsk \
  --allowedTools Read Glob Grep "Edit(~/vault/wiki/dreams/**)" \
  --disallowedTools Bash WebFetch WebSearch Agent NotebookEdit \
                    "Read(**/.env*)" "Read(**/*.pem)" "Read(**/id_*)" \
  --add-dir ~/vault/wiki --add-dir ~/vault/personal/memory \
  --max-budget-usd "$BUDGET" --output-format text
  • Isolated from my config.--setting-sources project --strict-mcp-config means none of my hooks, plugins or MCP servers load, so the command file's text (frontmatter stripped) is passed in as the prompt.
  • Least privilege. Edits are allowed only inside the folder the job owns, and there is no Bash or network.
  • A hard cost cap per run.

graphify by Graphify-Labs builds a knowledge graph of a codebase. A small wrapper of mine keeps one graph per repo plus a global graph, which an MCP server exposes for "does another repo already do X?" questions.

It's opt-in, though. When I measured it, the graph was no cheaper than grep for normal lookups, and a week of nudging the agent toward it produced 0 graph queries in 67 sessions. It's worth it for "who depends on X" lists, cross-repo duplication and component maps, and not much else.

The agent marks its own phases (tt agent begin|touch|end <project> <issue> <phase>) with tt, a time tracker CLI built by Linus Sköld with Jan Löfberg — all credit for tt goes to them. Hooks inject the contract at session start and on every prompt, because prose instructions get dropped once the context fills up.

If it must… Use a… Why
happen every time, no matter what hook Instructions in prose get skipped once the context fills up; a hook doesn't
be knowledge the model pulls in when relevant skill Only the one-line description sits in context; the body loads on demand. The description is the trigger, so I write it like a routing rule
be a ritual I start on purpose slash command Explicit, repeatable, and never fires by accident
be shared, versioned or reused across machines plugin Bundles skills, agents, commands and hooks behind one install
reach live external data MCP server Docs, graphs or a browser the model can't otherwise see
apply only to certain file types path-scoped rule Loads only when the session touches a matching file, so it costs nothing elsewhere

Path-scoped rules are markdown files in ~/.claude/rules/ (or a repo's .claude/rules/) with a paths: glob in the frontmatter:

---
name: dotnet-conventions
description: Conventions and validation commands for any C#/.NET change.
paths: ["**/*.cs", "**/*.csproj", "**/*.sln*", "**/*.razor", "**/*.cshtml"]
---
Existing repo conventions win over everything in this kit. Read neighbouring code first.

My .NET rule loads in .NET repos and costs zero context everywhere else. Its first line matters most: the repo's own conventions beat anything I've written down. (template)

A PostToolUse hook validates the YAML frontmatter every time a skill, command or agent file is edited. A skill with broken frontmatter doesn't error; it just never loads, which is easy to miss for weeks.

My plugins live in one marketplace repo, andreasheige/claude-plugins, so they're versioned in git and installed like any third-party plugin (/plugin marketplace add andreasheige/claude-plugins):

guardrails andplan-gate: sections 1 and 2, packaged with tests (deny/allow cases, the canary both ways, and every plan-gate exit path). #

devsecops-workflow: the dual-loop workflow above, plus some skills I reach for outside it:

  • dogma : iterative reasoning as solve → critic → refine, repeated until the critic finds nothing substantive.dogma-deep fans out to three agents with different framings first.
  • cost-aware-orchestration : when Claude dispatches subagents, it picks each one's model by task complexity, using real pricing, instead of letting every subagent inherit Opus.
  • verify-before-done : never say "done" or "try it yourself" without actually running the thing.
  • git-workflow : Conventional Commits, branch naming, rebase only.

dotnet-kit: path-scoped .NET / C# knowledge (modern C#, ASP.NET Core, EF Core, testing, observability, containers) that puts repo conventions first. Every code sample in a skill is compile-verified before it may ship:

dotnet build -warnaserror
dotnet format --verify-no-changes
dotnet test -- --filter-not-trait "Category=Docker"
python3 check-samples.py   # every C# block in a SKILL.md must appear, line for line, in the compiled samples

A skill that teaches code the model then copies had better compile.

Third-party plugins I run: claude-hud (status line), diagram-design (editorial diagrams as HTML/SVG), codex (hands a stuck problem or a second review to Codex) and claude-code-warp (Warp's official plugin: hooks that send native Warp notifications when Claude finishes, needs permission or sits idle, so I can leave parallel sessions running and get pinged).

My own:

  • progress-board: a dependency-free Node script that renders a live kanban from a markdown plan's checkboxes ([ ] ,[~] @agent ,[x] ), so I can see what each parallel agent is working on.
  • audit-claude: a one-time, read-only audit of the whole setup (CLAUDE.md, settings, hooks, MCP, skills, plugins) that produces a prioritised clean-up report.
  • figma-make-export: gets a Figma Make prototype's source onto disk when Figma's own download is missing or broken.
  • tt-time-logging: the time-logging contract from section 6.

Vendored:

  • tare: finds where my tokens went when I hit a usage limit.
  • kubernetes-skill: catches the usual Kubernetes hallucinations (missing limits, insecure defaults, API drift).
  • graphify: with its description rewritten to make it opt-in, see section 5.

I also use the skills that ship with CLI tools I run (herdr for terminal multiplexing, xirp for session management).

  • /plan-gate : section 2.

  • /wiki-ingest ,/wiki-query ,/wiki-dream ,/wiki-dream-review : section 4. The headless jobs run the same command files, so I can run any step by hand while debugging.

  • /agent-teams : toggles the experimental Agent Teams mode with tmux split panes.

  • microsoft-learn: official docs and code samples, so .NET and Azure answers come from the source, not from memory.

  • graphify-global: the cross-repo code graph (section 5).

  • claude-in-chrome: drives my real browser for UI checks.

  • ollama-browser-test: an experiment with a local Ollama model drivingbrowser-use for simple click-through tests. It's good at simple flows and unreliable at drag and drop, and it's still on trial.

  • Autograph (MIT): turns git commits into a daily chronicle in an Obsidian vault, using local Ollama. It's the capture layer the wiki ingests from.

  • The model invented timestamps. The headless ingest wrote log headings at plausible but made-up times (09:30, 13:00 … 17:00). A post-run script now overwrites them with the real run time.Lesson: anything a headless model can't observe, it will make up. Stamp facts from the outside.

  • Privacy origin judged per session instead of per fact. Tagging a whole session as work-derived sent general lessons into a silo no other session reads. Now each proposed fact is judged on its own, with a safety net: a "general" fact that names work material is forced back to work-only.

  • Forcing a tool doesn't make it useful. Graph-query nudges cost context every session and saved nothing in paired runs.Measure before you force a tool on the agent.

  • My own guardrail blocked my own job. The push rule blocked any command mentioningorigin/main , including harmless ones. Guardrails need tests with realistic commands, not just the dangerous ones.

  • Silent truncation. A 12-hour session was quietly cut to its first 40 minutes during capture. Check the size of what a pipeline outputs, not just whether it succeeded.

── more in #ai-agents 4 stories · sorted by recency
── more on @claude code 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/my-claude-code-setup…] indexed:0 read:16min 2026-10-21 · —