type: learnings_registry entry_prefix: LRN schema: id: LRN-XXX date: YYYY-MM-DD pattern: string (what was observed, abstracted) context: string (where/when it happened - concrete) future_application: string (when to recall this) rules:
| ID | Date | Pattern | Applies to |
|---|---|---|---|
| LRN-001 | 2026-04-22 | rtk shape-compression breaks pipes |
any pipeline chaining rtk curl/cat/read into jq, python -c, awk |
| LRN-002 | 2026-04-23 | Moving report-file paths requires grepping bash READS, not just WRITES | any refactor that moves a generated file used by a dispatcher |
| LRN-003 | 2026-04-27 | Claude Code disable* settings use sentinel string "disable", not boolean |
any change to permissions.defaultMode or related blocker keys |
| LRN-004 | 2026-04-27 | framer-motion was rebranded motion in Nov 2024 — different packages per framework |
any new project recommending an animation lib; auditing legacy imports |
| LRN-005 | 2026-05-03 | claude plugin install does NOT enable — separate claude plugin enable required |
every plugin installer that targets ALWAYS-ON status |
| LRN-006 | 2026-05-03 | caveman-shrink (and any MCP middleware proxy) is non-functional without an upstream wrapper |
any MCP middleware/proxy package — never claude mcp add it bare |
rtk shape-compression silently breaks downstream parsersrtk) intercepts stdout and returns a schematized/compressed representation instead of the raw payload, every downstream parser breaks silently — because the user (or the LLM) never sees rtk's output, only the parser error.rtk curl replaces raw JSON output with a tokenized version, regardless of TTY vs pipe. Claude Code hooks auto-rewrite curl → rtk curl, so the behavior is impossible to anticipate without knowing the hook.exclude_commands=["curl"] in ~/.config/rtk/config.toml, or rtk proxy. See BLK-001.test -s X.md, grep ... X.md, wc -l X.md — these refs are invisible if you only grep for "write" or "output path"..claude/audits/ refactor (commit 5c5e82c). First pass: I updated write paths across 5 skills (seo/geo/harden/validate/code-clean) and 3 agents. The user asked for a verify-gate. They re-grepped and found 10+ bare bash refs (e.g. test -s HARDEN.md, grep -oE ... VALIDATE.md) I had missed — the dispatchers were broken (looking at project root while the agent was writing to .claude/audits/). Fixed in commit 5c5e82c (bundled with the same commit).grep -rn "HARDEN\.md") in addition to the full path — to catch bare bash usages.test, grep, wc, cat, head), search for those verbs explicitly.disable* settings use the sentinel string "disable", not a booleandisableAutoMode, disableBypassPermissionsMode) use the literal string "disable" as a sentinel. The key being absent means the feature is available; the value "disable" is what turns the blocker on. Any other value (including false, true, null) has no effect — the doc explicitly states this.permissions.defaultMode to "auto" while disableAutoMode: "disable" was still present would have failed at startup ("auto mode unavailable"). The naming disable<Foo>: "disable" reads ambiguously — easy to assume it's a boolean toggle and leave the key in place.defaultMode, audit the matching disable* key in the same permissions block. If present with value "disable", remove it.bypassPermissions mode and disableBypassPermissionsMode.1421578, doc https://code.claude.com/docs/en/settings.framer-motion rebranded motion (Nov 2024) — different packages per frameworkframer-motion was renamed motion in November 2024. The rename is not just cosmetic: it bundles React (motion/react), Svelte, and vanilla-JS support under a single npm package, while Vue gets its own parallel package motion-v. The legacy package framer-motion still installs and works but is in maintenance mode — recommending it in a new framework default would lock projects into legacy import paths from day one. Detection of "is animation already covered" must therefore include both names plus the broader anim ecosystem (gsap, lottie-react, react-spring, popmotion, @formkit/auto-animate) to avoid double-installs./init-project and /onboard. Initial user phrasing was "framer-motion" (the old name they remembered). Picking the package name without verifying the rename would have shipped legacy imports in every new scaffold.motion (import { motion } from 'motion/react').motion-v (separate package, separate API).motion — use react-native-reanimated (motion targets the DOM).framer-motion and motion keys in package.json deps; treat either as "animation already covered".@org/lib, fork) is common in JS land.lib/animation-lib-check.sh, BDR-005.claude plugin install does NOT enable — claude plugin enable is a separate stepclaude plugin install --scope user name@source only copies the plugin into ~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/. It does NOT write name@source: true into the user's settings.json:enabledPlugins map. Without an explicit claude plugin enable name@source, the plugin sits dormant — installed but unloaded. This is symmetric with claude plugin disable, which keeps the cache and only removes the enabledPlugins entry.security-guidance and superpowers were ✘ disabled in claude plugin list despite the project's install-plugins.sh summary banner declaring them "ALWAYS ON". Root cause: install_plugin() only ran claude plugin install, never enable. The bug had stayed invisible because a hardcoded printf "│ ✅ ON : security-guidance rtk superpowers │" in session-start.sh printed the same names regardless of actual state — the lying banner agreed with the lying install.claude plugin install with claude plugin enable name@source (idempotent — no-op if already enabled).enabledPlugins[name@source] === true in settings.json, NOT by the presence of the cache dir. The pattern is implemented in lib/detect-plugins.sh:plugin_enabled() (filesystem grep, no subprocess).2ec7935, lib/detect-plugins.sh:plugin_enabled, install-plugins.sh:enable_plugin().caveman-shrink (and any MCP middleware proxy) needs an upstream wrapper to functioncaveman-shrink compresses prose fields). Running them bare via claude mcp add proxy-name -- npx -y proxy-pkg registers a server that errors immediately with "missing upstream command" — every health check fails, and Claude Code reports the MCP as broken until a human intervenes. The CLI claude mcp add doesn't validate that the configured command launches a working stdio MCP, so the bad registration silently lands.claude mcp add caveman-shrink -- npx -y caveman-shrink and prints "registered. wrap an upstream by editing the mcpServers entry". Following that flow leaves the user with a permanently failing MCP entry until they realize they have to edit ~/.claude.json manually.Future application:
For any MCP that is a proxy/middleware (read package docs for "upstream", "wraps", "proxy"), register it under a DERIVED name <proxy>-<upstream> with the upstream baked into the args. Example for caveman-shrink wrapping the filesystem server:
claude mcp add caveman-shrink-fs --scope user -- \
npx -y caveman-shrink npx -y @modelcontextprotocol/server-filesystem /path
Detection of "is this MCP correctly set up?" must look for the derived name (caveman-shrink-*), not the bare proxy name. Bare-name registration is treated as broken.
Default install scripts should NOT auto-register middleware MCPs — print the snippet for the user to choose an upstream. See install-plugins.sh STEP 5.5.
Reference: commit 9b20b84, lib/detect-plugins.sh:detect_caveman_shrink, install-plugins.sh STEP 5.5 MCP block.