{"slug": "my-claude-code-setup-guardrails-a-plan-gate-shared-memory-a-self-maintaining-and", "title": "My Claude Code setup: guardrails, a plan gate, shared memory, a self-maintaining wiki that dreams, and the toolbox around it", "summary": "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.", "body_md": "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.\n\n**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](https://github.com/andreasheige/claude-plugins)** (MIT), with a step-by-step \"start here\".\n\nDon't install all of this. The order I'd recommend:\n\n1. **One repo, one recurring task.** Something you do every week, not a greenfield demo.\n2. **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).\n3. **Plans with a check per step,** and`/plan-gate` so the agent finishes them (section 2).\n4. **A skill only when the same pain shows up a second time.** Not before.\n5. **Prune.** Every line of CLAUDE.md and every skill description is paid for on every turn.\n\nMemory, the wiki, the graph and time logging (sections 3–6) are add-ons. Add one when a concrete annoyance asks for it.\n\n| Layer | What it does | Pieces | \n|---|---|---|\n| 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 | \n| 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 | \n| 3. Memory | Durable facts that Claude Code, Codex CLI and Copilot CLI all read and write | Plain markdown files in an Obsidian vault | \n| 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 | \n| 5. Code knowledge | A knowledge graph per repo and a global graph across repos | [graphify](https://github.com/Graphify-Labs/graphify) plus a small fleet wrapper | \n| 6. Time | The agent logs its own time per phase of work | [`tt`](https://github.com/linus-skold/timetracker-rs) with hooks | \n| 7. Toolbox | Knowledge and rituals loaded only when needed | Skills, slash commands, a personal plugin marketplace, MCP servers | \n\n``` php\nflowchart LR\n  S[Claude / Codex / Copilot sessions] -->|commits, activity| A[Autograph capture]\n  S -->|transcripts| X[dream-extract<br/>scrub secrets, digests]\n  X --> P[propose facts<br/>Sonnet, evidence quotes]\n  P --> J[judge facts<br/>Opus, fresh context]\n  J -->|ticked, next day| G{evidence gate}\n  G -->|quote found| M[(shared memory<br/>markdown + git)]\n  A --> W[wiki ingest]\n  M --> W\n  W --> I[(wiki: concepts,<br/>entities, sources)]\n  M -->|read at session start| S\n  I -->|/wiki-query| S\n```\n\n`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:\n\n- **Destructive commands:**`sudo` ,`rm -rf` (it suggests`trash` ),`mkfs` ,`dd`\n- **Git:**`git merge` (I only rebase), plain`--force` ,`reset --hard` , and pushes to main/master\n- **Infrastructure:**`terraform apply/destroy` and`az … delete`\n- **Credentials:** reading`~/.ssh` ,`~/.aws` ,`.netrc` , Keychains or`.env` files, through either Bash or the Read tool\n\n```\ndeny() {\n  jq -n --arg reason \"$1\" \\\n    '{hookSpecificOutput:{hookEventName:\"PreToolUse\",permissionDecision:\"deny\",permissionDecisionReason:$reason},systemMessage:$reason}'\n  exit 0\n}\n\nif echo \"$cmd\" | grep -qE '\\bgit\\s+merge\\b' && \\\n   ! echo \"$cmd\" | grep -qE '\\bgit\\s+merge\\s+--(abort|continue|quit)\\b'; then\n  deny \"git merge is blocked (rebase-only policy). Use 'git fetch && git rebase origin/main' instead.\"\nfi\n```\n\nThe deny reason tells the model what to do instead, so a block steers it rather than leaving it stuck.\n\n**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.\n\n**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.\n\n**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.\n\n**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.\n\nClaude's hooks only see Claude. A global `core.hooksPath` applies the same policy to every commit on the machine, whoever or whatever made it:\n\n- `commit-msg` enforces Conventional Commits.\n- `pre-merge-commit` rejects merge commits (rebase only).\n- `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.\n\nGotcha: 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.\n\nMy whole config directory is a private repo with a **deny-by-default** `.gitignore`:\n\n```\n# ~/.claude mixes portable config with transcripts, shell snapshots, session\n# env, caches and credentials. An allowlist of exclusions would leak the first\n# time Claude Code added a new runtime directory, so everything is ignored\n# unless it is explicitly re-allowed below.\n*\n!.gitignore\n!CLAUDE.md\n!settings.json\n!hooks/\n!hooks/**\n!skills/\n!skills/**\n!commands/\n!commands/**\n```\n\n`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.\n\nAgents love to stop and ask \"should I continue?\". `/plan-gate plans/foo.md` arms a Stop hook that reads the plan:\n\n```\nif (/^\\s*BLOCKED:/m.test(planText)) allow();\n// otherwise, while \"- [ ]\" or \"- [~]\" items remain:\nJSON.stringify({ decision: \"block\", reason:\n  `The plan ${plan} still has ${items.length} open item(s). Next: \"${items[0]}\". ` +\n  `Keep going: do it, verify it, tick it (- [x]). ` +\n  `Only if you need the user (...) add a line \"BLOCKED: <reason>\" to the plan, then stop.` })\n```\n\n- **No stored state.** The hook finds the latest`/plan-gate` command in the transcript, so`/clear` or a new session resets it.\n- **When to stop.**`BLOCKED:` is reserved for real ambiguity, ask-first areas (auth, payments, PII, migrations, infra) and outward actions (push, PR).\n- **Safety limits.** At most 10 forced continuations per prompt, and it fails open on errors.\n\n**What a gate-able plan looks like** ([full example](https://github.com/andreasheige/claude-plugins/blob/main/starter/plans/example-plan.md)):\n\n```\n# Add rate limiting to the public API\nGoal: … Non-goals: …\n## Design\n## Open decisions\n## Steps\n- [x] 1. Read the existing middleware chain and config loading\n      → verify: list the files touched by step 2 in this plan.\n- [~] @agent-1 2. Add the limiter middleware with config-driven limits\n      → verify: unit test — 61st anonymous request in a window returns 429.\n- [ ] 3. Run the real server and hit it with `hey -n 100`\n      → verify: observed 429 count matches the limit (paste the output).\n## Review\n```\n\nEvery 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.\n\nCheap habits that cut cost more than any tool did:\n\n- **`/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.\n- **Low effort for routine work** (mechanical edits, config, log triage). Save full thinking for design and review.\n- **`/context` early** in a big session, to see what CLAUDE.md and MCP tool definitions already cost before real work starts.\n- **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.\n\nA personal plugin encodes the full development flow:\n\n- **Outer loop:** PRD → architecture → slice the epic into tickets.\n- **Inner loop, per ticket:** prime only the relevant files → atomic plan with a check per step → implement → validate → fresh-context review → rebase and land.\n\nWhat makes it work:\n\n- **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.\"*\n- **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.\n- **The architecture reviewer always runs in a new context,** never in the session that wrote the code.\n\n```\n4. Goal-driven execution. Every non-trivial step should be expressible as\n   `[step] → verify: [check]`. If you can't state the check, you don't\n   understand the step yet.\n5. Undo when adrift. If a change goes sideways, revert to the last known-good\n   checkpoint rather than layering corrective prompts on a confused context.\n6. Iterate on rules. If you correct the same mistake more than once in a\n   session, fix the standing rule or skill, not just the in-session symptom.\n```\n\nEach 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:\n\n```\nvault/\n  Memory.md                     # format + rules\n  personal/memory/memory.md     # index, one line per fact\n  personal/memory/<fact>.md     # one fact per file\n  projects/<repo>/memory/...    # per-repo facts\n---\nname: terminal-is-warp\ndescription: Never hard-code Terminal.app; read __CFBundleIdentifier\ntags: [type/gotcha, tool/any]\nmetadata: { added_by: claude-code, date: 2026-10-20 }\n---\n<the fact>\n**Why:** ...\n**How to apply:** ...\n```\n\nEach tool finds it differently:\n\n- **Claude Code:** a section in the global`CLAUDE.md` .\n- **Codex:**`~/.codex/AGENTS.md` is a symlink to a shared`AGENTS.md` .\n- **Copilot CLI:**`COPILOT_CUSTOM_INSTRUCTIONS_DIRS` points at the same folder.\n\n**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. Loading every fact every session would cost tens of KB of context per turn and drown the relevant ones. ([template](https://github.com/andreasheige/claude-plugins/tree/main/starter/memory))\n\nBecause it's plain files in git, I can read, diff and revert anything an agent wrote.\n\nThe wiki follows Karpathy's LLM-wiki pattern with a layout Obsidian can read:\n\n- **Pages:**`sources/` ,`entities/` ,`concepts/` ,`answers/` , an`index.md` and an append-only`log.md` .\n- **Origin tag:** every page carries an`origin:` tag (`dev | work | personal` ). Work-derived pages never leave the wiki folder.\n- **Schedule:** a daily launchd job runs headless ingest in batches.\n- **Lint:** a weekly lint pass runs on a local Ollama model.\n\nEvery morning:\n\n1. **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.\n2. **Propose** (Sonnet, $2 budget). At most 10 candidate facts, each a gotcha, decision, convention or preference, each with`evidence: <digest> \"<verbatim quote>\"` .\n3. **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.\n4. **Wait a day.** Ticked candidates are applied on the*next* run, so I have a veto window. A file with nothing ticked expires after 7 days.\n5. **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.\n\nThe model can't write a fact that isn't backed by something actually said in a session.\n\n```\nclaude -p --model \"$MODEL\" \"$prompt\" \\\n  --setting-sources project --strict-mcp-config \\\n  --permission-mode dontAsk \\\n  --allowedTools Read Glob Grep \"Edit(~/vault/wiki/dreams/**)\" \\\n  --disallowedTools Bash WebFetch WebSearch Agent NotebookEdit \\\n                    \"Read(**/.env*)\" \"Read(**/*.pem)\" \"Read(**/id_*)\" \\\n  --add-dir ~/vault/wiki --add-dir ~/vault/personal/memory \\\n  --max-budget-usd \"$BUDGET\" --output-format text\n```\n\n- **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.\n- **Least privilege.** Edits are allowed only inside the folder the job owns, and there is no Bash or network.\n- **A hard cost cap per run.**\n\n[graphify](https://github.com/Graphify-Labs/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.\n\nIt'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.\n\nThe agent marks its own phases (`tt agent begin|touch|end <project> <issue> <phase>`) with [tt](https://github.com/linus-skold/timetracker-rs), a time tracker CLI built by [Linus Sköld](https://github.com/linus-skold) with [Jan Löfberg](https://github.com/janlofberg-knowit) — 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.\n\n| If it must… | Use a… | Why | \n|---|---|---|\n| happen every time, no matter what | **hook** | Instructions in prose get skipped once the context fills up; a hook doesn't | \n| 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 | \n| be a ritual I start on purpose | **slash command** | Explicit, repeatable, and never fires by accident | \n| be shared, versioned or reused across machines | **plugin** | Bundles skills, agents, commands and hooks behind one install | \n| reach live external data | **MCP server** | Docs, graphs or a browser the model can't otherwise see | \n| apply only to certain file types | **path-scoped rule** | Loads only when the session touches a matching file, so it costs nothing elsewhere | \n\n**Path-scoped rules** are markdown files in `~/.claude/rules/` (or a repo's `.claude/rules/`) with a `paths:` glob in the frontmatter:\n\n```\n---\nname: dotnet-conventions\ndescription: Conventions and validation commands for any C#/.NET change.\npaths: [\"**/*.cs\", \"**/*.csproj\", \"**/*.sln*\", \"**/*.razor\", \"**/*.cshtml\"]\n---\nExisting repo conventions win over everything in this kit. Read neighbouring code first.\n```\n\nMy .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](https://github.com/andreasheige/claude-plugins/tree/main/starter/rules))\n\nA 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.\n\nMy plugins live in one marketplace repo, **[andreasheige/claude-plugins](https://github.com/andreasheige/claude-plugins)**, so they're versioned in git and installed like any third-party plugin (`/plugin marketplace add andreasheige/claude-plugins`):\n\n- \n**guardrails** and**plan-gate:** sections 1 and 2, packaged with tests (deny/allow cases, the canary both ways, and every plan-gate exit path).\n- \n**devsecops-workflow:** the dual-loop workflow above, plus some skills I reach for outside it:\n  - `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.\n  - `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.\n  - `verify-before-done` : never say \"done\" or \"try it yourself\" without actually running the thing.\n  - `git-workflow` : Conventional Commits, branch naming, rebase only.\n- \n**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:\n\n```\ndotnet build -warnaserror\ndotnet format --verify-no-changes\ndotnet test -- --filter-not-trait \"Category=Docker\"\npython3 check-samples.py   # every C# block in a SKILL.md must appear, line for line, in the compiled samples\n```\n\n A skill that teaches code the model then copies had better compile.\n\nThird-party plugins I run: [claude-hud](https://github.com/jarrodwatts/claude-hud) (status line), [diagram-design](https://github.com/cathrynlavery/diagram-design) (editorial diagrams as HTML/SVG), [codex](https://github.com/openai/codex-plugin-cc) (hands a stuck problem or a second review to Codex) and [claude-code-warp](https://github.com/warpdotdev/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).\n\nMy own:\n\n- **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.\n- **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.\n- **figma-make-export:** gets a Figma Make prototype's source onto disk when Figma's own download is missing or broken.\n- **tt-time-logging:** the time-logging contract from section 6.\n\nVendored:\n\n- **[tare](https://github.com/kelviq/tare):** finds where my tokens went when I hit a usage limit.\n- **[kubernetes-skill](https://github.com/LukasNiessen/kubernetes-skill):** catches the usual Kubernetes hallucinations (missing limits, insecure defaults, API drift).\n- **graphify:** with its description rewritten to make it opt-in, see section 5.\n\nI also use the skills that ship with CLI tools I run (herdr for terminal multiplexing, xirp for session management).\n\n- `/plan-gate` : section 2.\n- `/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.\n- `/agent-teams` : toggles the experimental Agent Teams mode with tmux split panes.\n\n- **microsoft-learn:** official docs and code samples, so .NET and Azure answers come from the source, not from memory.\n- **graphify-global:** the cross-repo code graph (section 5).\n- **claude-in-chrome:** drives my real browser for UI checks.\n- **ollama-browser-test:** an experiment with a local Ollama model driving`browser-use` for simple click-through tests. It's good at simple flows and unreliable at drag and drop, and it's still on trial.\n\n- **[Autograph](https://github.com/andreasheige/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.\n\n- **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.*\n- **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.\n- **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.*\n- **My own guardrail blocked my own job.** The push rule blocked any command mentioning`origin/main` , including harmless ones. Guardrails need tests with realistic commands, not just the dangerous ones.\n- **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.", "url": "https://wpnews.pro/news/my-claude-code-setup-guardrails-a-plan-gate-shared-memory-a-self-maintaining-and", "canonical_source": "https://gist.github.com/andreasheige/700405cb6d3489ffb8867bf6a4c51f6c", "published_at": "2026-10-21 12:30:49+00:00", "updated_at": "2026-10-23 13:12:26.066316+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-safety"], "entities": ["Claude Code", "Anthropic", "andreasheige/claude-plugins", "Codex CLI", "Copilot CLI", "Obsidian", "graphify", "Andreas Heige"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/my-claude-code-setup-guardrails-a-plan-gate-shared-memory-a-self-maintaining-and", "markdown": "https://wpnews.pro/news/my-claude-code-setup-guardrails-a-plan-gate-shared-memory-a-self-maintaining-and.md", "text": "https://wpnews.pro/news/my-claude-code-setup-guardrails-a-plan-gate-shared-memory-a-self-maintaining-and.txt", "jsonld": "https://wpnews.pro/news/my-claude-code-setup-guardrails-a-plan-gate-shared-memory-a-self-maintaining-and.jsonld"}}