From cc93aaba0cf8e22dbd4c086696dec0b19feda24e Mon Sep 17 00:00:00 2001 From: bastien Date: Tue, 22 Sep 2026 03:20:25 +0000 Subject: [PATCH] chore(memory): BDR-094 LRN-159 BLK-021, journal + TODO 2026-09-22 --- .claude/memory/blockers.md | 6 ++++ .claude/memory/decisions.md | 8 +++++ .claude/memory/journal.md | 3 +- .claude/memory/learnings.md | 7 ++++ .claude/tasks/TODO.md | 66 +++++++++++++++++++++++++++++++++++++ 5 files changed, 89 insertions(+), 1 deletion(-) diff --git a/.claude/memory/blockers.md b/.claude/memory/blockers.md index 21dfa9c..a5a6fcd 100644 --- a/.claude/memory/blockers.md +++ b/.claude/memory/blockers.md @@ -242,3 +242,9 @@ rules: - **Status**: resolved (A: ext hooks only terminals born after activation → install ext THEN start/re-attach session; B: Code app volume 0 in Windows mixer). - **Lesson**: two independent client faults presented as one symptom ("nothing works"). Splitting probe = run signal in FRESH terminal + play VS Code's own sound preview. Preview bypasses terminal/BEL/hook/dtach/ext → isolates renderer audio in one step. Do that FIRST next time, before any server-side archaeology. - **Reference**: [[BLK-019]] bell-only variant (resolved differently — setting alone insufficient here), [[LRN-145]] terminalSequence-not-/dev/tty pattern. Silent-degradation class [[LRN-047]]. + +## BLK-021 — Bash tool dead mid-session ("every command exits 1"): /tmp usrquota blown by a dead session's probe HOMEs — 2026-09-22 +- **Friction**: previous session on `feature/21st-cli-migration` lost its shell before tests + commit: every Bash call, `echo` included, returned 1. Its harness file `imptest2/step8d-test.sh` landed as 0 bytes. +- **Real cause** (strong evidence, not reproduced on purpose): `/tmp` = tmpfs 7.4 GB mounted `usrquota`; `/tmp/claude-1000/-home-bchanot-Documents-claude/fefd277c-…/scratchpad` holds 5.9 GB of sandbox HOMEs (`pinprobe/` 2.1 GB, `pinrc/` 1.6 GB, `imp1 impg imptest sbx1 sbx2 v3.2.0 v3.6.1 v4.0.5 …`) from the impeccable pin probes. `dd` 40 MB to `/tmp/claude-1000` → "Disk quota exceeded" (EDQUOT) while `df` still shows 1.6 GB avail. Same write to `~/.cache` OK. Bash tool + `mktemp` + heredocs live in /tmp → all die together. This session: first impeccable probe failed with `Quota exceeded (os error 122)` on the installer's `/tmp/impeccable-update-*` staging, same cause. +- **Solution**: this round ran everything with `TMPDIR=~/.cache/imp-probe/tmp` (probe, harness, `make test`). Durable fix = delete the dead session's scratchpad: `rm -rf /tmp/claude-1000/-home-bchanot-Documents-claude/fefd277c-e143-4d51-b589-a566641079b5` (agent's `rm -rf` on /tmp denied by the classifier → user action). Rule for probes: sandbox HOMEs that pull npm/node payloads go under `~/.cache//`, never the /tmp scratchpad, and get removed at the end of the session. +- **Status**: open until the user frees /tmp. Links [[BDR-094]], [[LRN-159]]. diff --git a/.claude/memory/decisions.md b/.claude/memory/decisions.md index cf542b8..987b0f1 100644 --- a/.claude/memory/decisions.md +++ b/.claude/memory/decisions.md @@ -1188,3 +1188,11 @@ Branch feature/user-writing-web-rules, UNMERGED (human gate). - **Alternatives rejected**: project-scope install (`/.claude/skills`) — Claude Code would ALSO scan it as project skills in this repo = every 21st skill listed twice; add the pack to `link.sh`'s `EXTERNAL_SKILLS` — that loop force-creates symlinks, resurrecting a default-disabled pack on every `make link`; all 7 skills in the design profiles — publishing flows cost 2 descriptions/session for a workflow the user does not run; `ask` entries for the publish verbs — inert under auto ([[LRN-153]], [[BDR-090]]/[[BDR-092]] already retired that tier). - **Status**: accepted. Verified: toggle enable/disable/restore/idempotent round-trip (7 skills), `profile.sh show design`, gate INCOMPLETE→names `21st` with the two commands, gate PATH repair proven under `env -i PATH=/usr/bin:/bin` with an nvm-stub, `profile-set-managed.test.sh` 17/17, `make test` (2 pre-existing FAILs, gitleaks binary absent on this host), shellcheck clean. OPEN for the user: `npm i -g @21st-dev/cli && 21st login` — the deny rule `Bash(npm install -g *)` means the agent cannot run it. - **Reference**: `install-plugins.sh` Step 8.7, `update-all.sh` 7.4, `lib/toggle-external.sh`, `lib/profile.sh`, `lib/profiles/*.profile`, `lib/design-tool-gate.sh`, `lib/design-gate.md`, `CLAUDE.global.md`, `README.md`, `settings.json`, `.gitignore`, `.gitleaks.toml`, `.env.example`, `link.sh`. Supersedes the operative parts of [[BDR-059]] (the 4 `mcp__magic__*` ask entries) and the magic instance of [[BDR-026]]/[[BDR-057]]; [[BDR-025]]'s required-manual class stands, its magic example does not. Links [[LRN-158]], [[LRN-110]]. + +## BDR-094 — impeccable: global-scope install through the repo symlinks, pin + @latest fallback, output-read failure check +- **Date**: 2026-09-22 +- **Decision**: `install-plugins.sh` Step 8d + `update-all.sh` run `npx -y impeccable@ skills install -y --providers=claude --scope=global --no-hooks` straight through the `~/.claude/{skills,agents}` symlinks → lands in `skills/impeccable` + `agents/impeccable-*.md` (both gitignored, machine-owned). No staging, no `mv`. Precondition guard: both symlinks must already point into the repo, else "run make link first". Pin failure → `@latest` + loud "bump plugins.lock.json" warn. Profile-parked copy stays parked (install to live slot, `mv` back to `skills-disabled/`). Success = rc 0 AND installer output free of `Download failed|Could not check for skill updates` (`imp_install`, mirrored in both scripts, sets `IMP_FAIL`). Pin 3.2.0 → 4.1.0 (CLI only; skill dist 4.3.1 + engine 0.1.5 own tracks). `link.sh` `EXTERNAL_SKILLS` drops impeccable; `skills-external/impeccable/` gone. `lib/design-gate.md` §5: suggest `/impeccable init` once when frontend project lacks `PRODUCT.md`. +- **Why**: (1) 3.2.0 skill dist gone upstream → rc 1 → `make plugin` printed "run manually" forever. (2) `--scope=project` + staged `mv` moved skill dir only, dropped the 4 subagents the same run wrote. (3) Global scope IS the repo install under the symlink model; staging bought nothing. (4) rc lies once a copy exists. Probe 2026-09-22, sandbox HOME, real installer: 4.1.0 then 3.2.0 → rc 0, "Could not check for skill updates: invalid zip data … Existing skills were left unchanged"; same-pin rerun → rc 0, "Skills are up to date (v4.3.1)"; both leave SKILL.md byte-identical (same mtime, same sha). +- **Alternatives rejected**: before/after skill-version compare (first idea) → cannot separate rotted-pin no-op from up-to-date no-op, identical files + rc 0 both times → false warn on every rerun. `--force` → re-downloads ~15 MB engine + dist on every `make plugin`, and the CLI's own update check already refreshes without it. Shared `lib/impeccable.sh` for `imp_install` → deferred: two mirrored 12-line helpers vs new lib + test; revisit at a third caller. Project-scope install inside this repo → Claude Code scans `.claude/skills` too = skill listed twice, shadows the global copy (seen live, TODO T6). +- **Status**: accepted. Verified: harness on extracted Step 8d, sandbox HOME, real installer, 4/4: fresh install (skill 4.3.1, 4 agents); rotted pin over a copy → fallback fires; same pin rerun → no false warn; parked + rotted → fallback, returned to `skills-disabled/`. `make test` green minus 2 pre-existing T16a (gitleaks absent), shellcheck clean. `update-all.sh` block: `bash -n` + shellcheck only, same helper, not run end to end. +- **Reference**: `install-plugins.sh` Step 8d, `update-all.sh`, `plugins.lock.json`, `.gitignore`, `link.sh`, `lib/design-gate.md` §5. Links [[LRN-159]], [[LRN-158]] (21st: opposite case, installer refuses symlinks → stage), [[LRN-077]] (pin doctrine), [[BLK-021]]. diff --git a/.claude/memory/journal.md b/.claude/memory/journal.md index 50a265e..6bd95be 100644 --- a/.claude/memory/journal.md +++ b/.claude/memory/journal.md @@ -483,4 +483,5 @@ rules: - Blocker: documented `21st install-skill` refuses the `~/.claude/skills` symlink → staged install under a throwaway HOME (LRN-158). - Gate: `magic`+MAGIC_API_KEY required-manual slot → the `21st` CLI; publish verbs moved to `autoMode.soft_deny` (ask inert under auto). - BDR-093, LRN-158. `make test` green except 2 pre-existing gitflow FAILs (gitleaks binary absent on this host). Branch UNMERGED — human gate. - +- impeccable install repaired ([[BDR-094]]): global scope through the symlinks, 4 agents kept, pin 3.2.0 → 4.1.0 with @latest fallback, design-gate §5 `/impeccable init` hint. Residue probed: rotted pin over an existing copy exits 0 → `imp_install` reads the installer output ([[LRN-159]]); before/after version compare rejected (identical no-op). Harness 4/4, sandbox HOME, real installer. +- Previous shell death traced: /tmp tmpfs usrquota blown by 5.9 GB of dead-session probe HOMEs ([[BLK-021]], open, user frees). Tests + harness ran with TMPDIR under ~/.cache. `make test` green minus 2 pre-existing T16a, shellcheck clean. Committed on feature/21st-cli-migration, UNMERGED. `skills/synced/` (claude.ai synced skills, 4.4 MB) untracked + unignored, left for the user. diff --git a/.claude/memory/learnings.md b/.claude/memory/learnings.md index 321f024..a8a373c 100644 --- a/.claude/memory/learnings.md +++ b/.claude/memory/learnings.md @@ -1499,3 +1499,10 @@ Rule: when editing a doctrine file under structure locks, grep the test's lock s - **Also**: read the npm tarball, not the vendor's web page. 21st.dev's `/mcp` and `/llms.txt` still document the MCP `init --client` flow with an API key; the package README states the CLI supersedes it. `curl registry.npmjs.org/` + untar + read `README.md`/`dist` answered every question (commands, exit codes, where files land) that the site got wrong. - **Future application**: any vendor installer that writes into `~/.claude`, `~/.config` or `~/.agents` on this machine. Probe first with a fake HOME containing the symlink, before wiring it into `install-plugins.sh` — the failure is instant and unambiguous. - **Reference**: [[BDR-093]], `install-plugins.sh` Step 8.7, `update-all.sh` 7.4. Links [[LRN-034]] (run the real thing), [[BLK-014]]-class symlink/self-heal issues. + +## LRN-159 — A pin whose payload is fetched at install time rots: pin + fallback, and read the installer's output, not its exit code +- **Date**: 2026-09-22 +- **Context**: `impeccable@3.2.0` still on npm, but `skills install` downloads the skill dist at run time and that release's zip is gone → "Download failed: invalid zip data". Strict pin = `make plugin` fails forever, prints "run it yourself". Second layer: once a copy exists, same CLI exits 0 on the same failure ("Could not check for skill updates … Existing skills were left unchanged"), indistinguishable by rc, by SKILL.md version or by mtime/sha from "Skills are up to date". +- **Pattern**: two classes of npm pin. (a) self-contained package → pin freezes behaviour, rc is truth. (b) package that fetches its payload at install time (impeccable, ctx7, `skills add` style) → pin freezes only the fetcher; payload can vanish or drift. For (b): pin + `@latest` fallback + loud "bump the lock" warn, never pin-or-die. And when the tool has an "already installed" branch, capture stdout+stderr and match the failure text; rc and before/after compare both read "unchanged" for a no-op AND for a swallowed failure. +- **Future application**: any `install-plugins.sh` step whose pinned tool downloads something at install time. Probe both HOME states (clean, copy present) before trusting rc. Cheap recipe: sandbox HOME with the repo-shaped symlinks, pinned install twice, then the rotted pin; diff rc + output + `stat`/`sha256sum` of the landed file. +- **Reference**: [[BDR-094]], `install-plugins.sh` Step 8d `imp_install`, `update-all.sh`. Links [[LRN-077]] (why pin), [[LRN-034]] (run the real thing), [[LRN-158]]. diff --git a/.claude/tasks/TODO.md b/.claude/tasks/TODO.md index c0329ca..3753a98 100644 --- a/.claude/tasks/TODO.md +++ b/.claude/tasks/TODO.md @@ -1,5 +1,71 @@ # TODO +## 2026-09-22 — impeccable install repaired: global scope + agents + rotted pin (feature/21st-cli-migration) +User: `make plugin` never installs impeccable, it just prints "run it +yourself"; running it by hand needs `--scope=global` to land right, and then +`/impeccable init` is still required. Three separate defects, all confirmed: +1. **Pin rotted.** `npx -y impeccable@3.2.0 skills install` → `Download + failed: invalid zip data`, rc 1. The CLI fetches its skill dist at install + time and that release's artifact is gone. 3.6.1 / 4.0.5 / 4.1.0 all work. + That rc 1 is the "run manually" warn the user sees. +2. **Wrong scope + half the payload dropped.** The step staged + `--scope=project` in a tmpdir and `mv`'d only the skill dir, silently + discarding the 4 `impeccable-*` subagents the installer also writes. + `--scope=global` writes `~/.claude/skills/impeccable` + + `~/.claude/agents/impeccable-*.md`, and both are symlinks INTO this repo, + so a global install is the repo install. Verified in a sandbox HOME. +3. **`/impeccable init` never surfaced.** It writes per-project PRODUCT.md + (design context the skill reads); it runs in the agent chat, so install + can only announce it and the design gate has to check it. + +- [x] T1 install-plugins.sh Step 8d rewritten: global scope, no staging, + pin→latest fallback with a loud bump-the-lock warn, park-aware + (profile may hold impeccable in skills-disabled), symlink precondition + guard, agent count + skill version reported, init hint printed. + Harness-tested against a fake HOME with repo-shaped symlinks: happy + path OK, park/restore OK. Caught + fixed there: `find` stops at the + `~/.claude/agents` symlink without `-L`, so the agent count read 0 + while 4 agents were installed. +- [x] T2 update-all.sh impeccable block: same shape. `bash -n` only, NOT + run end to end. +- [x] T3 plugins.lock.json: 3.2.0 → 4.1.0 + honest note (pin covers the CLI + only; skill dist 4.3.1 and engine 0.1.5 have their own tracks). +- [x] T4 .gitignore: `agents/impeccable-*.md` (machine-owned, tracked dir); + drop `skills-external/impeccable/`. link.sh: impeccable out of + EXTERNAL_SKILLS (nothing to symlink any more). `git check-ignore` + confirms both paths. +- [x] T5 lib/design-gate.md §5: suggest-only PRODUCT.md / `/impeccable init` + check, same shape as the §4 animation-library check. +- [x] T6 duplicate project-scope install: already gone at resume (user ran + `rm -rf .claude/skills .claude/agents` before restarting). +- [x] T7 CHANGELOG (Added/Changed/Fixed) + BDR-094 + LRN-159 + BLK-021 + + journal. `make test` green except the 2 pre-existing gitflow T16a FAILs + (gitleaks binary absent on this host), shellcheck clean. Committed on + the branch, UNMERGED — human gate. + +**Residual, probed and fixed (round 3)**: with a copy already installed a +rotted pin DOES exit 0 ("Could not check for skill updates: invalid zip data +… Existing skills were left unchanged"), and so does a genuine rerun of a +good pin ("Skills are up to date (v4.3.1)"). Both leave SKILL.md +byte-identical, so a before/after version compare cannot separate them. +`imp_install` (Step 8d and update-all.sh) now captures the installer output +and fails on `Download failed|Could not check for skill updates`, whatever +the exit code. Harness on the extracted step, sandbox HOME, real installer: +fresh install; rotted pin over a copy → fallback fires; same pin rerun → no +false warn; parked copy + rotted pin → fallback, then returned to +skills-disabled/. update-all.sh: `bash -n` + shellcheck only. + +OPEN for the user: +- /tmp is a tmpfs with a per-user quota and the dead session's scratchpad + holds 5.9 GB of probe HOMEs. Writes to /tmp fail with EDQUOT: the likely + cause of the "every command exits 1" shell death (BLK-021). Free it: + `rm -rf /tmp/claude-1000/-home-bchanot-Documents-claude/fefd277c-e143-4d51-b589-a566641079b5` + (the agent's `rm -rf` under /tmp is denied). This round ran tests and the + harness with TMPDIR under ~/.cache. +- `skills/synced/` (4.4 MB, untracked, not ignored): claude.ai's synced + skills, written through the ~/.claude/skills symlink. Decide whether to + gitignore it; not touched here. + ## 2026-09-22 — 21st: magic MCP → CLI + skills (feature/21st-cli-migration) User: "remplacer pour 21st, il n'y a plus besoin de mcp / api, mais juste en cli". Upstream confirmed (`@21st-dev/cli` 1.17.1 README): the CLI supersedes