Refuses the footgun commands Claude's memory notes warn about, with the reason and the safe alternative

Private Claude Code marketplace for Tuncer's mods: function-hook plugins that run inside every Claude Code session on his machines (MacBook Pro, MacBook Air, therig, the 5090 PC, the 4090 work PC).
| Plugin | What it does |
|---|---|
memory-guards | Refuses the footgun commands Claude's memory notes warn about (for example impeccable update --help, routing shiftyeyegames.com DNS through cloudflared, /logout before cswap add) and says why, with the safe alternative. |
kb-health | Shows KB health in the status line on the machine that runs the KB. It stays quiet on machines without the KB snapshot job. |
machine-tag | Prefixes session titles with the machine's emoji tag (💻 MBP, 🪶 Air, 🎮 Rig, 🔥 5090, ⚡ 4090), so pinned sessions show which computer they run on. /machine-tag <emoji> <word> overrides it per machine. |
pin-everywhere | Pinning a session turns on its Remote Control once machine-tag has put the machine icon in the title, so the session shows by name and icon on every other computer and the phone. Unpinning turns it back off when this mod turned it on. A title is published when Remote Control connects, so after renaming a pinned session, unpin and pin it again to publish the new name. |
claude plugin marketplace add Shifty-Eye-Games/claude-mods
claude plugin install memory-guards@claude-mods
claude plugin install kb-health@claude-mods
claude plugin install machine-tag@claude-mods
claude plugin install pin-everywhere@claude-mods
Then turn on auto-update: in /plugin, open Marketplaces, pick claude-mods and enable auto-update, or set it in ~/.claude/settings.json:
"extraKnownMarketplaces": {
"claude-mods": {
"source": { "source": "github", "repo": "Shifty-Eye-Games/claude-mods" },
"autoUpdate": true
}
}
Each session start then pulls main. No versions are set, so every commit is an update.
The repo is private, so each machine needs GitHub access without a prompt: an SSH key GitHub knows (Claude Code tries ssh -T git@github.com first), or stored HTTPS credentials (gh auth login then gh auth setup-git, or Git Credential Manager on Windows). Without them the clone and the updates fail silently and the machine keeps its last copy.
Machines that loaded these mods from local folders before must drop those folders from CLAUDE_CODE_PLUGIN_DIRS in ~/.claude/settings.json, or each mod loads twice.
plugins/<name>/.claude plugin validate plugins/<name> and claude plugin test plugins/<name>.main. Versions are left unset on purpose, so each commit reaches every machine at its next session start.How mods work, and the engine rules that bite, are in Claude's memory note on Claude Code mods.
Auto-update is on, so whatever lands on main runs in every session on all four machines. Only org admins can push here (the org's default member permission is read). Keep it that way: never give anyone else write access.
hooks/register.ts 98 lines1import type { Register } from 'claude-code'
2
3// Footguns from Claude's memory notes, enforced instead of remembered.
4// block: always refused. bump: refused once with the reason, and the identical
5// command retried within RETRY_MS runs (for rules that need judgment).
6type Rule = { mode: 'block' | 'bump'; pattern: RegExp; unless?: RegExp; why: string }
7
8// A program name counts only at a word or path start, so quoted text (a grep
9// for the phrase) passes. ponytail: an unquoted echo or heredoc line still
10// trips it; write such text with the Write tool instead.
11const RULES: Rule[] = [
12 {
13 mode: 'block',
14 pattern: /(?:^|[\s;&|(\/])impeccable(@\S+)?\s+update\b[^;&|\n]*\s(--help|-h)\b/,
15 why: '`impeccable update --help` does not print help. It RUNS the update, auto-confirmed, for all three providers (~/.claude, ~/.cursor, ~/.agents). Read the README instead. (memory: reference_impeccable)',
16 },
17 {
18 mode: 'bump',
19 pattern: /(?:^|[\s;&|(\/])impeccable(@\S+)?\s+update\b/,
20 why: '`impeccable update` updates all three providers (~/.claude, ~/.cursor, ~/.agents), not only this one. (memory: reference_impeccable)',
21 },
22 {
23 mode: 'block',
24 pattern: /(?:^|[\s;&|(\/])cloudflared\b[^;&|\n]*\btunnel\s+route\s+dns\b[^;&|\n]*shiftyeyegames\.com/,
25 why: '~/.cloudflared/cert.pem is scoped to agentnous.ai, so this creates <host>.shiftyeyegames.com.agentnous.ai. Create the CNAME with `npx cf@latest dns records create --zone shiftyeyegames.com` (content <tunnel-id>.cfargotunnel.com, proxied). (memory: reference_cloudflare_cli)',
26 },
27 {
28 mode: 'block',
29 pattern: /(?:^|[\s;&|(\/])mlx-serve\s+serve\b/,
30 unless: /--host[\s=]+(127\.0\.0\.1|localhost|::1)\b/,
31 why: '`mlx-serve serve` binds 0.0.0.0 by default, which exposes the model to the network. Add `--host 127.0.0.1`. (memory: project_x_bookmarks)',
32 },
33]
34
35const BRANCH =
36 /(?:^|[\s;&|(\/])git\b[^;&|\n]*\s(checkout\s+-[bB]|switch\s+(-[cC]|--create|--force-create)|worktree\s+add\b[^;&|\n]*\s-[bB])\s/
37const FETCH = /(?:^|[\s;&|(\/])git\b[^;&|\n]*\s(fetch|pull)\b/
38const BRANCH_WHY =
39 'No successful `git fetch` in the last 30 minutes. Branching off a stale local main once duplicated already-merged work (#153). Fetch first, e.g. `TOK=$(gh auth token); git fetch "https://x-access-token:$TOK@github.com/<org>/<repo>.git" main`, then branch from FETCH_HEAD. (memory: feedback_refetch_before_branching)'
40const FRESH_MS = 30 * 60_000
41const RETRY_MS = 10 * 60_000
42
43export const register: Register = on => {
44 // ponytail: plain module state, reset by a reload or restart; the worst case
45 // is one extra bump. Not per repo: a fetch anywhere counts for 30 minutes.
46 let fetchedAt = -Infinity
47 let logoutAskedAt = -Infinity
48 const bumpedAt = new Map<string, number>()
49
50 on('tool.call', async ($, e, next) => {
51 const command = 'command' in e && typeof e.command === 'string' ? e.command : undefined
52 if (command === undefined) {
53 return next(e)
54 }
55
56 const now = await $.clock.now()
57 const hits = RULES.filter(rule => rule.pattern.test(command) && !rule.unless?.test(command))
58 const block = hits.find(rule => rule.mode === 'block')
59 if (block) {
60 return { deny: `${$.plugin.name}: ${block.why}` }
61 }
62
63 const bumps = hits.map(rule => rule.why)
64 if (BRANCH.test(command) && !FETCH.test(command) && now - fetchedAt > FRESH_MS) {
65 bumps.push(BRANCH_WHY)
66 }
67 if (bumps.length > 0) {
68 const askedAt = bumpedAt.get(command)
69 if (askedAt === undefined || now - askedAt > RETRY_MS) {
70 bumpedAt.set(command, now)
71 return {
72 deny: `${$.plugin.name}: ${bumps.join(' ')} Retry the identical command within 10 minutes to run it anyway.`,
73 }
74 }
75 bumpedAt.delete(command)
76 }
77
78 const ran = await next(e)
79 if (FETCH.test(command) && ran.deny === undefined && ran.isError !== true) {
80 fetchedAt = now
81 }
82
83 return ran
84 })
85
86 on('command.run', { command: 'logout' }, async ($, e, next) => {
87 const now = await $.clock.now()
88 if (now - logoutAskedAt < RETRY_MS) {
89 return next(e)
90 }
91 logoutAskedAt = now
92
93 return {
94 text: `${$.plugin.name}: /logout can revoke the refresh token cswap stored for this account. To change accounts use \`cswap switch <num|email>\`; after logging in to a new account run \`cswap add\`. Run /logout again within 10 minutes to log out anyway. (memory: reference_claude_swap)`,
95 }
96 })
97}
98