diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb17f2bf5..719b0bda7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,6 +87,13 @@ jobs: # the linter only blocks net-new undocumented compatibility debt. run: scripts/lint-legacy-markers.sh + - name: Lint no tracked node_modules + # Hard ban (#491) — nothing under a node_modules directory may be + # tracked. A committed symlink at such a path bypasses the ignore + # rules, clobbers populated installs on pull, and blocks worktree + # checkouts. Any tracked node_modules path segment fails. + run: scripts/lint-no-tracked-node-modules.sh + - name: Lint design tokens # ADR-047 forward-only ratchet — fails when a design-system category # (off-token spacing/radius/font, raw Colors.*, stray Color(0x..), diff --git a/desktop/electron/node_modules b/desktop/electron/node_modules deleted file mode 120000 index aff7d313c..000000000 --- a/desktop/electron/node_modules +++ /dev/null @@ -1 +0,0 @@ -/tmp/claude-1000/-home-ubuntu-embodylearn/74638504-a76b-41ae-bd64-c5e34c2e5d34/scratchpad/vaultfix-wt/desktop/electron/node_modules \ No newline at end of file diff --git a/desktop/node_modules b/desktop/node_modules deleted file mode 120000 index 177ee488e..000000000 --- a/desktop/node_modules +++ /dev/null @@ -1 +0,0 @@ -/tmp/claude-1000/-home-ubuntu-embodylearn/74638504-a76b-41ae-bd64-c5e34c2e5d34/scratchpad/vaultfix-wt/desktop/node_modules \ No newline at end of file diff --git a/scripts/lint-no-tracked-node-modules.sh b/scripts/lint-no-tracked-node-modules.sh new file mode 100755 index 000000000..236ca6f15 --- /dev/null +++ b/scripts/lint-no-tracked-node-modules.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# lint-no-tracked-node-modules.sh — fail if ANY node_modules path is tracked. +# +# Why: #491 — a commit landed two symlinks (mode 120000) at +# desktop/node_modules and desktop/electron/node_modules pointing at an +# absolute scratch path on the committer's machine. The normal +# node_modules ignore rules do NOT stop a tracked entry at that exact +# path, and the blast radius was real: +# - `git pull` replaced populated node_modules directories with the +# dangling symlink (installed deps silently gone, builds broke); +# - worktrees with a real node_modules directory could not check out +# main at all ("untracked working tree files would be overwritten"). +# +# There is no legitimate reason to track anything under a node_modules +# directory — not a symlink, not a vendored file (vendoring belongs in a +# deliberate location with a deliberate name). So this is a hard ban, not +# a ratchet: any tracked path containing a node_modules segment fails. +# +# Usage: +# scripts/lint-no-tracked-node-modules.sh # check (CI mode) +# +# Exit 0 clean; 1 with the offending paths listed. + +set -u +cd "$(dirname "$0")/.." + +# git ls-files lists every tracked path, symlinks included. Match +# node_modules as a full path segment (start/end anchored), so +# `desktop/node_modules`, `desktop/electron/node_modules`, or anything +# nested beneath such a directory all hit, while a path merely containing +# the substring (e.g. scripts/rebuild-node_modules.sh) does not. +hits="$(git ls-files | grep -E '(^|/)node_modules(/|$)' || true)" + +if [ -n "$hits" ]; then + echo "FAIL: tracked node_modules path(s) detected:" >&2 + echo "$hits" | sed 's/^/ /' >&2 + echo >&2 + echo "Nothing under a node_modules directory may be tracked — remove with:" >&2 + echo " git rm " >&2 + echo "(See #491 for the incident this guards against.)" >&2 + exit 1 +fi + +echo "lint-no-tracked-node-modules: clean"