- BDR-006: Caveman as 4th always-on plugin (output compression, sym with rtk's input compression). Full install rationale, alternatives rejected, caveats around path normalization + gitignore. - LRN-005: claude plugin install does NOT enable — explicit claude plugin enable required for ALWAYS-ON plugins. Pattern: read enabledPlugins, never hardcode plugin names in status displays. - LRN-006: caveman-shrink (and any MCP middleware proxy) needs an upstream wrapper. Bare registration fails health checks. Pattern: register under derived names (proxy-upstream), don't auto-register. - Journal: 2026-05-03 entry summarizing the session. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
11 KiB
11 KiB
| type | entry_prefix | schema | rules | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| learnings_registry | LRN |
|
|
Learnings registry (LRN)
Index
| 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 |
LRN-001 — rtk shape-compression silently breaks downstream parsers
- Date: 2026-04-22
- Pattern: when a tracking tool (
rtk) 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 seesrtk's output, only the parser error. - Context:
rtk curlreplaces raw JSON output with a tokenized version, regardless of TTY vs pipe. Claude Code hooks auto-rewritecurl→rtk curl, so the behavior is impossible to anticipate without knowing the hook. - Future application: for any tool that auto-rewrites standard commands, explicitly verify pipe behavior. Documented workaround:
exclude_commands=["curl"]in~/.config/rtk/config.toml, orrtk proxy. SeeBLK-001.
LRN-002 — Moving report-file paths requires grepping bash READS, not just WRITES
- Date: 2026-04-23
- Pattern: when moving the write path of a generated file (report, artifact, cache), you must also grep the places that READ that file — not only those that write it. Dispatchers (orchestrator skills that dispatch to an agent and then parse the result) typically contain bash commands like
test -s X.md,grep ... X.md,wc -l X.md— these refs are invisible if you only grep for "write" or "output path". - Context:
.claude/audits/refactor (commit5c5e82c). 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 commit5c5e82c(bundled with the same commit). - Future application:
- Before declaring a file-path migration "complete", grep the basename (
grep -rn "HARDEN\.md") in addition to the full path — to catch bare bash usages. - If the file is used in pipelines (
test,grep,wc,cat,head), search for those verbs explicitly. - Verify-gates save work: one extra round asked by the user forced exhaustive re-grepping. Without it, two dispatchers would have shipped broken.
- Before declaring a file-path migration "complete", grep the basename (
LRN-003 — Claude Code disable* settings use the sentinel string "disable", not a boolean
- Date: 2026-04-27
- Pattern: Claude Code blocker-style settings (
disableAutoMode,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 (includingfalse,true,null) has no effect — the doc explicitly states this. - Context: switching
permissions.defaultModeto"auto"whiledisableAutoMode: "disable"was still present would have failed at startup ("auto mode unavailable"). The namingdisable<Foo>: "disable"reads ambiguously — easy to assume it's a boolean toggle and leave the key in place. - Future application:
- Before changing
defaultMode, audit the matchingdisable*key in the samepermissionsblock. If present with value"disable", remove it. - Same logic for
bypassPermissionsmode anddisableBypassPermissionsMode. - Don't trust the doc's naming — read the value semantics. Sentinel strings beat booleans here because the harness can distinguish "unset" from "explicitly off" (admin policy).
- Before changing
- Reference: commit
1421578, dochttps://code.claude.com/docs/en/settings.
LRN-004 — framer-motion rebranded motion (Nov 2024) — different packages per framework
- Date: 2026-04-27
- Pattern:
framer-motionwas renamedmotionin 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 packagemotion-v. The legacy packageframer-motionstill 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. - Context: building animation-lib auto-install in
/init-projectand/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. - Future application:
- For React / Next.js / Remix / Astro+React / Svelte:
motion(import { motion } from 'motion/react'). - For Vue 3 / Nuxt:
motion-v(separate package, separate API). - For React Native: do NOT recommend
motion— usereact-native-reanimated(motion targets the DOM). - When auditing existing projects, check both
framer-motionandmotionkeys inpackage.jsondeps; treat either as "animation already covered". - Before adopting any "industry default" lib in a framework, verify the canonical package name is current — naming churn (rebrand, scope change
@org/lib, fork) is common in JS land.
- For React / Next.js / Remix / Astro+React / Svelte:
- Reference: helper
lib/animation-lib-check.sh, BDR-005.
LRN-005 — claude plugin install does NOT enable — claude plugin enable is a separate step
- Date: 2026-05-03
- Pattern: the Claude Code CLI splits "available" from "active" for marketplace plugins.
claude plugin install --scope user name@sourceonly copies the plugin into~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/. It does NOT writename@source: trueinto the user'ssettings.json:enabledPluginsmap. Without an explicitclaude plugin enable name@source, the plugin sits dormant — installed but unloaded. This is symmetric withclaude plugin disable, which keeps the cache and only removes the enabledPlugins entry. - Context: discovered while auditing why
security-guidanceandsuperpowerswere ✘ disabled inclaude plugin listdespite the project'sinstall-plugins.shsummary banner declaring them "ALWAYS ON". Root cause:install_plugin()only ranclaude plugin install, neverenable. The bug had stayed invisible because a hardcodedprintf "│ ✅ ON : security-guidance rtk superpowers │"insession-start.shprinted the same names regardless of actual state — the lying banner agreed with the lying install. - Future application:
- For any plugin meant to be ALWAYS ON, follow
claude plugin installwithclaude plugin enable name@source(idempotent — no-op if already enabled). - Detect "actually enabled" via
enabledPlugins[name@source] === trueinsettings.json, NOT by the presence of the cache dir. The pattern is implemented inlib/detect-plugins.sh:plugin_enabled()(filesystem grep, no subprocess). - Any banner / status display that claims a plugin is on must read state, never hardcode names. Hardcoded labels turn a single bug into two co-conspiring bugs that mask each other.
- For any plugin meant to be ALWAYS ON, follow
- Reference: commit
2ec7935,lib/detect-plugins.sh:plugin_enabled,install-plugins.sh:enable_plugin().
LRN-006 — caveman-shrink (and any MCP middleware proxy) needs an upstream wrapper to function
- Date: 2026-05-03
- Pattern: some MCP packages are middleware proxies, not standalone servers. They wrap an upstream MCP server and transform its responses (e.g.
caveman-shrinkcompresses prose fields). Running them bare viaclaude mcp add proxy-name -- npx -y proxy-pkgregisters 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 CLIclaude mcp adddoesn't validate that the configured command launches a working stdio MCP, so the bad registration silently lands. - Context: when adding caveman, the upstream installer auto-registers
claude mcp add caveman-shrink -- npx -y caveman-shrinkand 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.jsonmanually. - 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.shSTEP 5.5.
- For any MCP that is a proxy/middleware (read package docs for "upstream", "wraps", "proxy"), register it under a DERIVED name
- Reference: commit
9b20b84,lib/detect-plugins.sh:detect_caveman_shrink,install-plugins.shSTEP 5.5 MCP block.