forked from bchanot/claude
chore(memory): BLK-021/022, LRN-150/151, BDR-088, EVAL-029 — macOS port
Capitalizes the macOS port and the gstack Chromium deadlock, plus an
append-only correction to BLK-008 / LRN-038: their "ubuntu24.04 fallback
build" cause is refuted — macOS arm64 has a native Playwright 1.58.2 build,
no fallback, and the same hang reproduces. The real variable was the Node
version, and that wrong record misdirected this investigation for an hour.
EVAL-029 records two process failures worth keeping: the first fix
recommendation (pin node@22) was reversed only because the user asked
whether the browser was current — staleness had gone unpriced; and the grep
sweep returned empty twice while defects were present, once to `set -e`,
once to a pattern that could not match `${1,,}`.
Index rows added for all four registries. Pre-existing index drift
(BLK-018..020, LRN-144..149) left alone — backfilling means summarising
entries someone else wrote.
This commit is contained in:
@@ -37,6 +37,8 @@ rules:
|
||||
| BLK-015 | 2026-07-03 | `gitflow_finish` ignored its `<type> <name>` args → merged the CHECKED-OUT branch not the one named → wrong-branch merge (audit LOT3) | resolved |
|
||||
| BLK-016 | 2026-07-04 | rtk compression PATH-dead 30 days — 6/5070 Bash commands compressed (~460K tokens missed); installer sources cargo env so its own check passes, Claude tool shell never gets ~/.cargo/bin | resolved |
|
||||
| BLK-017 | 2026-07-17 | Bing Webmaster API unusable for a multi-client agency: OAuth swamp (localhost redirect refused, rotated single-use refresh tokens race our parallel dispatch), API key = wrong model (client-owned sites) | open/deferred |
|
||||
| BLK-021 | 2026-09-13 | gstack Chromium install hangs forever on macOS: Playwright 1.58.2 deadlocks on Node 26 mid-extraction (39/333 files, all threads idle) | resolved |
|
||||
| BLK-022 | 2026-09-13 | macOS bash 3.2 + BSD userland: six silent defects, most fail-OPEN (SSRF guard, commit scope guards, gate criteria) | resolved |
|
||||
|
||||
---
|
||||
|
||||
@@ -116,6 +118,7 @@ rules:
|
||||
- **2026-06-23 UPDATE — Solution REVERTED, status downgraded to UPSTREAM/open** (commit b9c3937): the `PLAYWRIGHT_HOST_PLATFORM_OVERRIDE` solution above does NOT work on 26.04. The fallback build downloads to 100% then HANGS at extraction (chrome binary never appears, no headless-shell download starts; reproduced on real machine + sandbox) → turned a 0.5s fast-fail into an install-blocking hang (user Ctrl+C). Reverted to the fast-fail (non-fatal; gstack OFF by default, browser only for /browse,/qa,screenshots). The earlier "verified ldd + headless render" was an isolated test on a sibling already-extracted build (rev 1228) — it masked the rev-1208 install-path hang. **Real fix = upstream**: gstack bumps Playwright to a version that lists ubuntu26.04. Until then gstack's browser is unavailable on 26.04, install completes cleanly. See [[LRN-038]] correction.
|
||||
|
||||
- **2026-06-23 FINAL — RESOLVED** (commit 3b8ffb1): gstack browser now works on Ubuntu 26.04. Two layers fixed: (1) bumped gstack's pinned Playwright 1.58.2 → 1.61 (`bun add playwright@latest` in the submodule; 1.61 ships a native ubuntu26.04 build — chromium rev 1228), automated in the installer (`gstack_bump_playwright_if_unsupported`, idempotent, OS-gated); (2) `GSTACK_CHROMIUM_NO_SANDBOX=1` to work around the AppArmor userns restriction (`sysctl kernel.apparmor_restrict_unprivileged_userns=1`), persisted to `.bashrc` + installer Step 9 (sysctl-gated). Verified end-to-end: `browse goto https://example.com` → "Navigated (200)". Caveat: the Playwright bump is a local submodule edit, reset by `git submodule update`, re-applied by the next install. See [[BDR-029]], [[LRN-040]].
|
||||
- **2026-09-13 CORRECTION — the diagnosis above is wrong, the fix was right**: the rev-1208 "downloads 100% then HANGS at extraction" was imputed to the `ubuntu24.04` FALLBACK build. REFUTED on macOS arm64, where Playwright 1.58.2 has a NATIVE build and no fallback exists: the SAME hang reproduces with the SAME signature (39/333 files, every thread idle). The real variable is the NODE version — 1.58.2 deadlocks on a runtime newer than itself, platform-independently. The 1.58.2→1.61 bump did resolve 26.04, but for a reason not recorded here: it also cleared that Node incompatibility. See [[BLK-021]] / [[LRN-150]].
|
||||
|
||||
---
|
||||
|
||||
@@ -242,3 +245,25 @@ 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 — gstack Chromium install hangs forever on macOS (Playwright 1.58.2 x Node 26) — 2026-09-13
|
||||
- **Friction**: `make plugin` froze at step 2/10. Log ends mid-Chromium install: 100% of 162.3 MiB downloaded, then nothing — no error, no timeout, no progress. Steps 3-10 (RTK, GSD, marketplace plugins, link.sh, shell profile) never ran.
|
||||
- **Real cause**: gstack's lockfile froze Playwright **1.58.2** (published 2026-02-06) → chromium rev 1208. Its extraction DEADLOCKS under **Node 26.5.0**: both processes (`playwright install` + child `oopDownloadBrowserMain.js`) fully idle — main thread in `kevent`, V8 AND libuv workers in `__psynch_cvwait`, 0% CPU, 2.5s CPU total — stuck at exactly 39/333 files. Zip fully downloaded and intact (170206961 B). NOT network, disk (396Gi free), Gatekeeper, quarantine (none set), nor Intego VirusBarrier — an AV block parks a thread in `write`; none was. PW 1.58.2 declares `engines: node >=18`, so Node 26 is formally SUPPORTED: the incompatibility is undeclared upstream.
|
||||
- **Proof (3-way, one variable moved)**: Node 26 x PW 1.58.2 = hang (2/2 reproductions); Node 22.23.1 x PW 1.58.2, same command + same rev = OK (336 files); Node 26 x PW 1.63.0 = OK (347 files, 360MB, Chrome 153.0.8010.12).
|
||||
- **Solution**: bump gstack Playwright 1.58.2 → 1.63.0 (rev 1243). In-range, not a pin break — `package.json` declares `"playwright": "^1.58.2"`; only `bun.lock` froze it. Installer now pre-installs the browser under a deadline with bump-retry ([[BDR-088]]). The submodule edit stays LOCAL (reset by `git submodule update`, re-applied by the next install — the [[BDR-029]] pattern).
|
||||
- **Status**: resolved. Gate verified: `chromium.launch()` → `LAUNCH OK — Chromium 153.0.8010.12`, rc 0.
|
||||
- **Corrects upstream record**: [[BLK-008]] / [[LRN-038]] imputed this exact signature on Ubuntu to the `ubuntu24.04` FALLBACK build. REFUTED: macOS arm64 has a NATIVE 1.58.2 build, no fallback exists there, and the same hang reproduces with the same signature. The real variable is the NODE version. The 1.58.2→1.61 bump did fix 26.04, but for a reason not recorded there — it also cleared the Node incompatibility.
|
||||
- **Cost of the wrong record**: it sent the 2026-09-13 investigation hunting fallback builds first. A fix that WORKS can freeze a WRONG cause.
|
||||
|
||||
## BLK-022 — macOS bash 3.2 + BSD userland: six fail-OPEN or silent-no-op defects — 2026-09-13
|
||||
- **Friction**: repo moved to macOS (Darwin 25.6, arm64). `make test` red across 5 suites, and several guards PASSED while doing nothing at all.
|
||||
- **Real cause**: `/bin/bash` is **3.2.57** and `#!/usr/bin/env bash` resolves to it (no Homebrew bash on PATH). Every failure is silent:
|
||||
- `${1,,}` (bash 4.0+) in `lib/url-guard.sh` → "bad substitution", subshell exits 1 = "not local" → the SSRF guard returned rc 0 for localhost, 127.x, 10.x, 192.168.x, 172.16-31.x, **169.254.169.254** and metadata.google.internal. FAIL-OPEN on every Mac; `url-guard.test.sh` recorded it as 13x "got[0] want[2]".
|
||||
- `mapfile` in the 3 surgical-commit helpers → empty arrays → scope guards fail-OPEN, commits degrade to "nothing pending — no-op" while reporting success.
|
||||
- `declare -A` in `hooks/session-start.sh` → every plugin cost read 0 → the >50%-budget warning could never fire.
|
||||
- `timeout` (coreutils, absent from a stock macOS) in `lib/gates.sh` → exit 127 → EVERY criterion recorded NOT-MET whatever the check did.
|
||||
- GNU `sed -i` x3 in `install-plugins.sh` → BSD sed errors → aborts the installer under `set -euo pipefail`.
|
||||
- Test-side GNU-isms: `touch -d`, BSD `wc -l` padding (`got[ 48] want[48]`), `/bin/grep` (does not exist on macOS), `stat -c`, `sed -i` + `\n` in the replacement.
|
||||
- **Solution**: `_read_lines_into` (portable mapfile), `shopt -s nocasematch` (bash 3.1+, keeps the no-fork property), `case` for plugin costs, resolved timeout binary + pure-bash fallback, `_sed_inplace` + awk. Split over 5 branches.
|
||||
- **Status**: resolved. Every suite 0 failures except `gitflow-test.sh` (13 failures, PRE-EXISTING and unattributed: 92/14 on pristine develop vs 93/13 after — nothing worsened, one case better). shellcheck 1 finding before and after (pre-existing SC2016).
|
||||
- **Bonus found while porting**: the orphan-comment cleanup `{N; /^\n$/d;}` in `install-plugins.sh` was a no-op on EVERY platform — after `N` the pattern space starts with '#', so the `^\n$` anchor pair never applied. Rewritten in awk and tested.
|
||||
|
||||
@@ -97,6 +97,7 @@ rules:
|
||||
| BDR-085 | 2026-08-25 | User permanent rules: writing-style always-on in rules/, web build+security path-scoped | accepted |
|
||||
| BDR-086 | 2026-08-26 | darwin: threshold gates full loops; verified defects fixed regardless of unit score (paired-validated, batched checkpoint) | accepted |
|
||||
| BDR-087 | 2026-09-03 | Stop hook = attention signal only, never control flow; one script for Notification + Stop | accepted |
|
||||
| BDR-088 | 2026-09-13 | gstack browser: guarded pre-install (900s deadline + Playwright bump-retry), not a pinned Node | accepted |
|
||||
|
||||
---
|
||||
|
||||
@@ -1123,3 +1124,11 @@ Branch feature/user-writing-web-rules, UNMERGED (human gate).
|
||||
- **Guard vs prior refusal**: [[BDR-083]] (unlazy review, GATE 0) REFUSED a Stop hook using `decision:"block"` (forces continuation, inverts human gates). THIS Stop hook returns `terminalSequence` + `suppressOutput` only, exit 0, zero control-flow effect. Signal ≠ control. Do not read the refusal as banning Stop outright.
|
||||
- **Status**: accepted.
|
||||
- **Reference**: [[LRN-146]] event-coverage gap, [[BLK-020]] client-side faults, [[LRN-145]] terminalSequence pattern. Verified live: turn-end + AskUserQuestion both ring; `permission_prompt` unexercisable under `defaultMode: auto`.
|
||||
|
||||
## BDR-088 — gstack browser: guarded pre-install (deadline + bump-retry), not a pinned Node
|
||||
- **Date**: 2026-09-13
|
||||
- **Decision**: `install-plugins.sh` pre-installs gstack's Chromium BEFORE `./setup`, wrapped in a portable timeout (`_run_with_timeout`, `GSTACK_BROWSER_TIMEOUT=900`). On deadline: `bun add playwright@latest` in the submodule, retry once, then WARN (non-fatal — gstack is OFF by default; only `/browse`, `/qa` and screenshots depend on it). New helpers: `_run_with_timeout` (pure bash — coreutils `timeout` is absent from a stock macOS), `_gstack_bun_install` (extracted, shared with `gstack_bump_playwright_if_unsupported`), `_gstack_pw_install`.
|
||||
- **Why**: REACT to the observed failure (a hang) instead of PREDICTING it — no version table to maintain, and it self-heals for future Node x Playwright pairs. The deadline alone fixes the worst defect: an installer that hangs forever with no message (it silently cut step 2/10 here, and forced a Ctrl+C on Ubuntu per [[BLK-008]]).
|
||||
- **Alternatives rejected**: pin `node@22` for gstack's setup (freezes Chrome at 145, EIGHT majors behind stable 153, adds a node@22 dep, and only masks the symptom — this was the FIRST recommendation, reversed once the browser-staleness was priced, see [[EVAL-029]]); widen `gstack_bump_playwright_if_unsupported`'s `/etc/os-release` gate with a Node-version table (brittle: `engines` declares no upper bound, so there is nothing authoritative to compare); document only (a clean cache re-hangs with no signal).
|
||||
- **Status**: accepted.
|
||||
- **Reference**: `install-plugins.sh` `_run_with_timeout` / `gstack_install_browser_guarded`. Guard proven by FORCED failure (hang → rc 124 in 6s, no orphaned `oopDownloadBrowserMain`, stderr clean after fd-park) and by a real run (browser present → `ok` in 5s, idempotent). [[BLK-021]], [[LRN-150]].
|
||||
|
||||
@@ -39,6 +39,7 @@ rules:
|
||||
| EVAL-025 | 2026-07-17 | opening seo/geo inventory (subagents): 7/7 verifiable claims false or overstated; real contact corrected all, 6 plan corrections + 4 features killed at measurement | keep |
|
||||
| EVAL-027 | 2026-08-24 | contract-gates behavioral RED: 16/16 fresh unprimed runs followed new doctrine (GATE 0 order, vacuous oracle, ABANDONED routing, scope temptation resisted) | keep |
|
||||
| EVAL-028 | 2026-08-26 | darwin v2.1 paired run 54 units: 60 paired verdicts 0 revert/tie; skeptics found 3 real residuals — engaged, not rubber-stamp | keep |
|
||||
| EVAL-029 | 2026-09-13 | macOS port: recommendation reversed by one user question (browser staleness unpriced); grep detector returned empty twice | keep |
|
||||
|
||||
---
|
||||
|
||||
@@ -267,3 +268,9 @@ Dogfood: 3 blind lenses attacked the v1 plan for the plan-challenge feature itse
|
||||
- **Method**: paired same-judge 3-majority per round (v2.1); judges live-exec where artifact executable (5 units: skills-perso, profile, plugin-pair, status-reporter, gitflow). Absolute scores triage-only. Totals main-thread (LRN-018 applied).
|
||||
- **Anomalies**: (1) 0 reverts/ties in 60 verdicts — homogeneous-better checked: skeptic lens found real residuals 3x (doctor.sh cost source, hotfix RULES leftover restore, FILE(S) new-marker) → judges engaged. (2) census lock RED on line-rewrap, make test caught → LRN-144. (3) head-pipe masked grep exit 2x → LRN-143.
|
||||
- **Action**: v2.1 paired = standard. Post-run absolute rescore skipped by design (would be judge-noise theater).
|
||||
|
||||
## EVAL-029 — macOS port: recommendation reversed by one user question; detector failed twice
|
||||
- **Date**: 2026-09-13/15. **Output**: 5 branches — SSRF guard restored, bash 3.2 portability, GNU/BSD coreutils, installer unblocked, BDR-019 sweep. 14 files.
|
||||
- **Method**: portability scan → the repo's OWN `make test` as oracle (each defect surfaced as a named assertion); hang → reproduce, `sample` BOTH pids, then a 3-way discriminating matrix (runtime x dep version). Fallback paths exercised by FORCING them (`GATES_TIMEOUT_BIN=""`, an injected hang).
|
||||
- **Anomalies**: (1) I recommended pinning node@22 and ranked the Playwright bump SECOND. The user asked only "est-ce la dernière version de chromium ?" — checking showed rev 1208 = Chrome 145 vs stable 153, AND `package.json` already declaring `^1.58.2` (bump in-range; only the lockfile froze it). Recommendation reversed. I had scoped the decision to "does it install" and never priced browser staleness, nor read the declared range before calling the bump a pin-break. (2) My grep sweep returned EMPTY twice and I nearly read it as clean — `set -e` killing the loop, then a pattern demanding a letter after `${` that missed `${1,,}`, the SSRF bug. Both caught by accident. (3) [[BLK-008]]'s recorded cause misdirected the first hour. (4) I over-investigated `gitflow-test.sh`'s 13 pre-existing failures instead of bounding the question early; the user had to redirect.
|
||||
- **Action**: when choosing BETWEEN fixes, read what the dep DECLARES vs what the lockfile froze, and price the side-effects of freezing a version (staleness, unpatched CVEs, render fidelity) — not just "does it unblock". A scan that finds NOTHING must first be proven able to find something known-present. Bound archaeology on pre-existing failures: establish "not caused by me, not worsened" and move on.
|
||||
|
||||
@@ -460,3 +460,9 @@ rules:
|
||||
- Post-merge regression: toast dead again after re-attach from a RESTORED terminal, bell fine. Root cause [[LRN-147]]: ext hooks only terminals born after its activation; `enablePersistentSessions` restores terminals before it. Fix = disable persistent sessions, or fresh terminal + `dtach -a`. Verified: 3/3 toasts on fresh pty.
|
||||
- Same-day counter-example broke that cause: second session's terminal deaf though created LATER, same window, ext global, shells identical. Trigger unknown; [[LRN-148]] adds the 5s pre-flight test + demotes LRN-147's mechanism claim.
|
||||
- Attention signal refined: per-event labels (BDR-087 follow-on), silence on non-attention events, and no turn-end signal while `background_tasks` non-empty ([[LRN-149]]). Payload dump beat the docs: `background_tasks` undocumented for Stop but present on the wire. Branch bugfix/notify-subagent-spawn.
|
||||
|
||||
## 2026-09-15
|
||||
- macOS port of the fork, on develop (repo moved from `/home/bchanot-ubuntu/…`; origin switched to git.bchanot.fr/bmottin/claude_mac). Root of everything: `/bin/bash` is 3.2.57 and `#!/usr/bin/env bash` resolves to it. Six defect classes, all SILENT ([[BLK-022]]) — worst is `${1,,}` turning `lib/url-guard.sh` into a pass-through for localhost/127.x/10.x/192.168.x/169.254.169.254 (SSRF guard fail-OPEN); then `mapfile` making the 3 commit guards fail-open + commits silent no-ops, `declare -A` zeroing the budget warning, missing `timeout` making EVERY gate criterion NOT-MET, GNU `sed -i` aborting the installer.
|
||||
- gstack Chromium hang = [[BLK-021]]: PW 1.58.2 deadlocks on Node 26 mid-extraction (39/333 files, ALL threads idle). Proven by a 3-way matrix moving one variable. Fixed by bump to 1.63.0 → Chrome 145 → 153. Installer now bounds that step ([[BDR-088]]). [[BLK-008]]/[[LRN-038]] diagnosis corrected — cause was Node, not the ubuntu24.04 fallback build.
|
||||
- 5 branches cut, NOT merged (gitflow human gate): bugfix/url-guard-ssrf-bash32, bugfix/macos-bash32-portability, bugfix/macos-gnu-coreutils, bugfix/macos-installer, chore/sweep-bdr019-makefile. Submodule resynced to develop's pointer (11de390), Playwright bump re-applied locally per [[BDR-029]].
|
||||
- Open: `gitflow-test.sh` 13 failures PRE-EXISTING and unattributed (92/14 pristine vs 93/13 after) — separate chantier. gitleaks absent from this machine (installer does not provide it) → T16a + `make scan-secrets` unavailable. Nothing pushed.
|
||||
|
||||
@@ -139,6 +139,8 @@ rules:
|
||||
| LRN-134 | 2026-07-17 | resolve-then-pin in stdlib http.client beats monkeypatching getaddrinfo — dual-stack, thread-safe, no requests; classify the OS-resolved IP not the URL text | closing SSRF/DNS-rebinding on any Python HTTP egress |
|
||||
| LRN-135 | 2026-07-17 | a prefix-only scan for a dangerous construct is bypassable by padding — scan the WHOLE document | refusing any hostile construct (DTD/directive/marker) before parse |
|
||||
| LRN-143 | 2026-08-26 | `cmd \| head \|\| fallback` — pipeline rc is head's (0), fallback dead; bounded output → drop head, else pipefail | any probe/fallback bash in skills before trusting `\|\|` |
|
||||
| LRN-150 | 2026-09-13 | Lockfile-pinned dep vs fast runtime: undeclared incompatibility HANGS, never errors; `engines` has no upper bound | any pinned tool that stalls — check dep publish date vs runtime release, and the DECLARED range vs the lock |
|
||||
| LRN-151 | 2026-09-13 | Porting to macOS: bash 3.2 makes guards fail-OPEN, not abort; and a scan finding nothing proves nothing | after any OS migration — run the suite first, audit guards before cosmetics, self-check the detector |
|
||||
|
||||
---
|
||||
|
||||
@@ -624,6 +626,7 @@ rules:
|
||||
- **Future application**: any pinned tool that hardcodes an OS allowlist breaks on a fresh OS upgrade. Look for a host-platform override env before bumping/forking the dep. Prove the fallback binary actually runs (`ldd` = no missing libs + a real headless render), not just that the download resolves.
|
||||
- **Reference**: `install-plugins.sh` `playwright_platform_override()`, commit 211c7d4. Linked to [[BLK-008]].
|
||||
- **2026-06-23 CORRECTION (override REVERTED, commit b9c3937)**: the override is NOT a usable fix on Ubuntu 26.04. It makes `playwright install` switch to the ubuntu24.04 fallback build, which downloads to 100% then HANGS at extraction (chrome binary never materializes; real machine + sandbox). Turned a 0.5s fast-fail into an install-blocking hang. The isolated proof (`ldd` + headless render) PASSED but used an already-extracted sibling build (rev 1228) — it masked the install-path hang in the real flow (rev 1208). **Sharpened lesson**: proving the binary launches in isolation is NOT proving the install path works — run the ACTUAL install command end-to-end (it must COMPLETE, not just "download resolves" nor "a binary launches"). The override technique stays valid in general, but the EXTRACTION/COMPLETE step is part of "does it work".
|
||||
- **2026-09-13 CORRECTION**: "the ubuntu24.04 fallback build hangs at extraction" is REFUTED as the cause. Same hang, same signature, on macOS arm64 where 1.58.2 ships a native build and no fallback is involved — so the cause is Playwright 1.58.2 deadlocking on a too-new Node, not the build. This entry cost real time: it sent the 2026-09-13 macOS investigation hunting fallback builds first. A fix that WORKS can freeze a WRONG cause. See [[BLK-021]] / [[LRN-150]].
|
||||
|
||||
---
|
||||
|
||||
@@ -1421,3 +1424,18 @@ Rule: when editing a doctrine file under structure locks, grep the test's lock s
|
||||
- **Fail-open**: field absent (older client) → still signal. Missed notification worse than extra one.
|
||||
- **Cross-session gotcha**: hook is user-scope, so EVERY session runs it. A single-file dump (`> file`) gets overwritten by another project's session — append JSONL and filter on `.cwd`. That accident proved `permission_prompt` fires with `message="Claude needs your permission"` (unexercisable in this session under `defaultMode: auto`).
|
||||
- **Future**: any hook needing turn-completion semantics must check background_tasks; "turn ended" ≠ "work done". Verified live: Stop with 0 tasks signals, Stop with 1 running subagent silent.
|
||||
|
||||
## LRN-150 — a lockfile-pinned dep vs a fast runtime: the undeclared incompatibility HANGS, it does not error
|
||||
- **Context**: 2026-09-13. gstack's lockfile-frozen Playwright 1.58.2 (Feb 2026) under Node 26.5.0 (Sept 2026). Chromium extraction deadlocks at 39/333 files, silently, forever. `engines: node >=18` claims support.
|
||||
- **Pattern**: `engines` is a CLAIM, not a test — an UPPER bound is almost never declared, so "too new" reads as "supported" and fails as a HANG, not an error. Diagnose with `sample <pid>` (macOS) or any stack dump: ALL threads idle (`kevent` + `__psynch_cvwait`, 0% CPU, libuv workers INCLUDED) = deadlock, nothing in flight; a thread parked in `write`/`read` would mean AV/FS/network instead — that one measurement ruled out Intego VirusBarrier in seconds. Then discriminate by moving ONE variable: same command + same revision under another runtime (`brew` keeps node@22 beside node@26).
|
||||
- **Read the declared range before calling it a pin**: `package.json` said `^1.58.2`, so 1.63.0 was already in range — only `bun.lock` froze it. A "bump" that needs no fork and breaks no contract was available the whole time.
|
||||
- **Corollary**: a fix that WORKS can freeze a WRONG cause in the registry. [[BLK-008]] blamed a fallback build; that record misdirected this investigation three months later. When a fix lands, record which variable was PROVEN, not the one suspected.
|
||||
- **Future**: pinned dep + hang → compare dep publish date vs runtime release date BEFORE blaming platform/network/AV. Any unattended install step that can hang needs a DEADLINE: silent-forever is strictly worse than failing loudly ([[BDR-088]]).
|
||||
|
||||
## LRN-151 — porting to macOS: the danger is fail-OPEN, and a scan that finds nothing proves nothing
|
||||
- **Context**: 2026-09-13. Repo moved Linux → macOS. `/bin/bash` = 3.2.57 = what `#!/usr/bin/env bash` resolves to. Six distinct defect classes ([[BLK-022]]).
|
||||
- **Pattern — the failure direction is what matters**: bash 3.2 does not abort on a bash-4 construct, it makes the SUBSHELL fail, and a guard whose "block" path is an exit code then reads as "allow". `${1,,}` turned an SSRF allowlist into a pass-through; `mapfile` turned scope guards into empty-array no-ops that still reported success; missing `timeout` turned every gate criterion into NOT-MET. Audit order: find the guards FIRST, ask what an errored subshell returns there, and only then chase cosmetics.
|
||||
- **Empirically catalogued surface** (bash 3.2 + BSD userland): `${var,,}`/`${var^^}`, `mapfile`/`readarray`, `declare -A`, `timeout` (coreutils), `sed -i` (needs a suffix; no `\n` in the replacement), `touch -d`, `stat -c`, `wc -l` (pads with spaces — breaks string compares), `/bin/grep` (macOS has only /usr/bin/grep), `readlink -f` (OK since Monterey), `sort -V` (OK).
|
||||
- **The test suite is the oracle**: the repo's own `make test` located every one of these faster than reading code, because each defect surfaced as a specific assertion. Port = run the suite, fix what reddens, re-run.
|
||||
- **Self-check the detector**: my grep sweep returned EMPTY twice and I nearly read it as "clean" — once because `set -e` killed the loop on the first no-match grep, once because the pattern demanded a letter after `${` and so missed `${1,,}` (a digit). A scan that finds NOTHING must first be shown to find something known-present. Both misses were caught by accident, not by method.
|
||||
- **Future**: after any OS migration, run the full suite before trusting any static sweep, and treat a 100%-of-a-category warning as a stale check rather than 100% non-compliance ([[BDR-019]] sweep, `doctor.sh` + `Makefile`).
|
||||
|
||||
Reference in New Issue
Block a user