Skip to content

tooling(slm-frontend): the shared SLM node_modules was emptied and now holds a link to itself — every worktree's SLM vue-tsc and vitest are dead #18406

Open

Description

@mrveiss

Related: #16912 (worktrees symlink the main checkout's node_modules), #17573 (an earlier break of the same shared install: mode 644, not traversable).

Observed 2026-10-12 (read-only)

  • The main checkout's autobot-slm-frontend/node_modules has 1 entry and no .bin, so vue-tsc and vitest are missing. The directory inode is 12288 bytes, so it previously held many entries.
  • That single entry is node_modules -> <main checkout>/autobot-slm-frontend/node_modules, a link to its own parent, created 11:23:53. The directory mtime is the same second.
  • autobot-frontend/node_modules is intact (721 entries, 85 bins).
  • It worked on 2026-10-11 (frontend session). Every worktree links to this directory, so SLM type-check and tests fail or time out in pre-push in every session. feat(approvals): one approval record, shared RiskTier, pending inbox query (#13421 S1) #18404's pre-push vue-tsc timed out at 159s.

What is known about the cause

  • The self-link: ln -s <main>/…/node_modules <dir>/node_modules creates exactly this link when <dir> resolves to the main checkout instead of a worktree, because ln places the link inside an existing directory. So the worktree link step most likely ran with the main checkout as its target.
  • The emptying: NOT determined. Three agents of the session that reported this say they ran no package-manager command today; the other sessions have not been asked.

Acceptance criteria

  • The worktree link step refuses to run when its target resolves to the main checkout, and refuses to create a link inside an existing real directory. There is a test for both.
  • Nothing a worktree does through the link can delete the shared install. Either package-manager commands refuse to run when node_modules is a link, or the hook detects it and stops with instructions. There is a test for this.
  • Pre-push reports "SLM frontend deps missing" as its own failure, not as a vue-tsc timeout.
  • The cause of the emptying is found or explicitly recorded as unknown, with the evidence that was checked.

Activity

  1. mrveiss commented on Oct 12, 2026

    @mrveiss
    OwnerAuthor

    What was checked (2026-10-12):

    • Who ran package-manager commands: none, by their own account. The master session's agents (5), the helper session and the frontend session all report no npm ci, npm install or package-manager run today. The only npx use was npx --no-install openapi-typescript at about 10:42 and 10:55, which only executes a binary.

    • Worktree links near 11:23: none. The SLM node_modules link timestamps of every live worktree are all ≤ 11:14:40, so no worktree was created or re-linked at 11:23:53.

    • Nearest event: at 11:22:08, git checkout -b ran in a frontend worktree, so its post-checkout hook ran. A subagent working in that worktree from 11:22 to 11:27 already found the SLM vitest and vue-tsc missing. So the emptying may PREDATE the 11:23:53 link.

    • The current post-checkout link step (section 7) cannot create this link as written:

      • it skips when the main root equals the target root;
      • it links only when the target ! -e, so it never creates a link inside an existing directory;
      • with GIT_DIR set, GIT_ROOT resolves to .git/worktrees, and [ -d $GIT_ROOT/$_proj ] fails.

      So the self-link came from something other than this hook, or from an older copy of it in some worktree's .git/hooks. Not determined.

    • Earlier main-checkout event: on 2026-10-11 at 23:47:58, a checkout ran in the main checkout by mistake and was restored at 23:48:13.

    Status: the cause of both the emptying and the link is unknown. The owner is restoring the install by hand. The ACs stand, especially the one that makes the hook refuse a target inside an existing real directory, and the one that detects an emptied shared install.

  2. mrveiss commented on Oct 12, 2026

    @mrveiss
    OwnerAuthor

    A second effect of this, in the pre-push hook: it misclassifies the dead install as a test failure, so AUTOBOT_PREPUSH_ALLOW_UNRUNNABLE=1 does not apply and the push is blocked outright.

    Observed on a merge-train push, verbatim:

    [pre-push WARN] vue-tsc (autobot-slm-frontend) COULD NOT RUN on this interpreter — allowed by AUTOBOT_PREPUSH_ALLOW_UNRUNNABLE=1
    [pre-push FAIL] vitest failures (autobot-slm-frontend, #5142):
    vitest.config.ts (8:16) [UNRESOLVED_IMPORT] Could not resolve '@vitejs/plugin-vue' in vitest.config.ts
    failed to load config from …/autobot-slm-frontend/vitest.config.ts
    ⎯⎯⎯⎯⎯⎯⎯ Startup Error ⎯⎯⎯⎯⎯⎯⎯⎯
    Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'vitest' imported from …/autobot-slm-frontend/node_modules/.vite-temp/vitest.config.ts.timestamp-…mjs
    [pre-push FAIL] Push BLOCKED.
    

    The same run classified vue-tsc as COULD NOT RUN, but not vitest. A vitest run that dies with failed to load config / Startup Error / ERR_MODULE_NOT_FOUND before collecting a single test ran nothing, and should be classified COULD NOT RUN like vue-tsc, not as a failure.

    The shared SLM node_modules currently holds only .vite-temp (no vitest).

    Acceptance criterion to add:

    • A vitest startup error with zero tests collected is classified as COULD NOT RUN, with a contrast test.
  3. mrveiss commented on Oct 12, 2026

    @mrveiss
    OwnerAuthor

    Owner: please prioritise this. It is now the single blocker on the merge pipeline.

    Current state: autobot-slm-frontend/node_modules still holds 1 entry, with no vitest and no vue-tsc.

    What it is blocking:

    What unblocks it: either option works.

    1. Restore the SLM frontend install, which agents may not do themselves (no installs into the checkout).
    2. Approve fixing the hook so that a vitest startup error with zero tests collected is classified COULD NOT RUN, like vue-tsc already is. (That is this issue's added AC.)
  4. added 2 commits that reference this issue on Oct 12, 2026
  5. mrveiss commented on Oct 12, 2026

    @mrveiss
    OwnerAuthor

    Restored on 2026-10-12 at the owner's request.

    • Removed the self-link autobot-slm-frontend/node_modules/node_modules (→ itself, created 11:23:53).
    • Ran npm ci --no-audit --no-fund in the main checkout: added 543 packages, rc=0.
    • node_modules/.bin/vue-tsc and .bin/vitest are present. npm run type-check rc=0, with 0 error TS.

    Root cause: still not established. I read the post-checkout link step (section 7). By its guards ([ -L dst ] && [ ! -e dst ] then rm, and [ ! -e dst ] then ln -s src dst), it should never write into an existing directory.

    Two hypotheses fit the evidence, and neither is verified:

    1. An install run inside a worktree. npm ci or npm install in a worktree, whose node_modules is a symlink to the main checkout's, would wipe the shared install through the link. That would explain the emptied directory.
    2. Two paths for one directory. If GIT_ROOT and MAIN_ROOT resolve to the same directory under different strings, _dst and _src are one directory. A run that happens while it's briefly missing could then link it to itself. That would explain the self-link.

    Suggested hardening:

    • In the hook: skip when realpath "$_dst" equals realpath "$_src".
    • For worktree installs: a guard that refuses npm ci or npm install when node_modules is a symlink, for example a preinstall check in both apps' package.json.
  6. mrveiss commented on Oct 12, 2026

    @mrveiss
    OwnerAuthor

    Cause found from the session transcripts, 2026-10-12.

    1. The wipe, 10:52:04 Riga (07:52:04Z).
      • In worktree wt-18394, the security(sso): SSO callbacks redirect with the session JWT in the query string — now live after #18322 #18394 subagent ran this in autobot-slm-frontend: npm ci --no-audit --no-fund && npx --no-install openapi-typescript openapi.json -o src/types/generated/api.ts --additional-properties.
      • That worktree's node_modules was the tech-debt(frontend): a frontend change in a fresh worktree has no local verification at all — not vitest, not vue-tsc #16912 symlink to the main checkout's install. npm printed npm warn reify Removing non-directory …/wt-18394/autobot-slm-frontend/node_modules, then added 543 packages into the worktree.
      • npm ci's cleanup ran through the symlink, and the shared install in the main checkout was emptied.
      • Timing fits:
        • SLM vitest ran fine in another worktree at ~08:37 Riga;
        • the main install was found already empty at 11:23:22 (the S0 subagent's ls …/AutoBot-AI/autobot-slm-frontend/node_modules/.bin/vitest: No such file);
        • no other package-manager command appears in any transcript in that window.
      • Not directly proven: that npm ci deleted the target's contents through the link, rather than only the link. Everything else is recorded.
    2. The self-link, 11:23:50 Riga.
      • The frontend session's S0 subagent ran ln -s $R/autobot-slm-frontend/node_modules autobot-slm-frontend/node_modules inside refined-ui-shell. There the destination already was a symlink to that directory, so ln created node_modules/node_modules inside the target.
      • That was mine. It was harmless to the contents, but it is the self-link that was found.

    Fix, so it can't recur:

    • Guard in both apps' package.json: a preinstall check that refuses npm ci or npm install when node_modules is a symlink, with the message: "run make frontend-setup in the main checkout".
    • Hook: the post-checkout link step skips when realpath of the destination equals the source.
    • Agent rule: never install in a worktree. The standard procedure is make frontend-setup from the main checkout (owner, 2026-10-12).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions