Root workspace configuration with 4 submodules: - codebase/ → lilith/platform-codebase - deployments/ → lilith/platform-deployments - tooling/ → lilith/platform-tooling - docs/ → lilith/platform-docs Tracks workspace config (package.json, turbo.json, bunfig.toml), CI workflows (.forgejo/), dev scripts, and instructions. Each submodule retains its own history and remote.
4.1 KiB
Node Modules Locking - Automated Hook Management
Problem
Original Issue: pnpm hooks in root package.json only run when executing pnpm install from the project root. When agents or developers run pnpm install from subdirectories (like codebase/features/analytics/backend-api/), the preinstall hook doesn't unlock node_modules, causing EACCES permission errors.
Error Example:
ERR_PNPM_LINKING_FAILED Error: EACCES: permission denied, unlink
'node_modules/rrweb-cssom/LICENSE.txt'
Why node_modules Are Locked
node_modules directories are intentionally read-only to prevent agents from accidentally editing installed packages instead of source code. This is a safety mechanism enforced by:
preinstallhook: Unlocks node_modules before pnpm installpostinstallhook: Locks node_modules after pnpm install
Correct workflow for modifying @lilith/* packages:
- Edit source at
~/Code/@packages/@category/package-name/ - Bump version, commit, publish to forge.nasty.sh
- Update consumer:
pnpm update @lilith/package-name
Solution
Add preinstall/postinstall hooks to all feature-level package.json files so that pnpm install works from any subdirectory.
Automated Script
Location: tooling/scripts/add-node-modules-hooks.ts
Usage:
# Via unified CLI (recommended)
./run dev add-node-modules-hooks
# Or directly
npx tsx tooling/scripts/add-node-modules-hooks.ts
What it does:
- Recursively finds all
package.jsonfiles incodebase/features/ - Excludes
node_modules/anddist/directories - Calculates correct relative path to
tooling/scripts/node-modules-lock.sh - Adds/updates preinstall and postinstall hooks
- Preserves all existing scripts
- Is idempotent (safe to run multiple times)
Example hooks added:
{
"scripts": {
"preinstall": "../../../../tooling/scripts/node-modules-lock.sh unlock",
"postinstall": "../../../../tooling/scripts/node-modules-lock.sh lock",
"...": "other scripts preserved"
}
}
Path Calculation
The script automatically calculates the correct relative path based on package.json depth:
| Package Location | Relative Path |
|---|---|
codebase/features/analytics/backend-api/package.json |
../../../../tooling/scripts/node-modules-lock.sh |
codebase/features/conversation-assistant/frontend-dev/e2e/mock-api/package.json |
../../../../../../tooling/scripts/node-modules-lock.sh |
When to Run
Run ./run dev add-node-modules-hooks when:
- Adding new features with package.json files
- Setting up a fresh clone of the repository
- Troubleshooting EACCES errors during pnpm install from subdirectories
- After receiving agent reports about permission errors
Verification
To verify hooks are working:
# From a feature subdirectory
cd codebase/features/analytics/backend-api/
# Check if path is valid
ls -la ../../../../tooling/scripts/node-modules-lock.sh
# Run pnpm install (should work without errors)
pnpm install
Statistics
- Total packages updated: 68
- Depth range: 4-7 levels from project root
- Idempotent: Re-running script only updates packages with incorrect/missing hooks
Manual Alternatives
If you can't run the script, you can:
-
Always run from root:
# From project root only pnpm install -
Manual unlock/lock:
# From root ./tooling/scripts/node-modules-lock.sh unlock-all cd codebase/features/analytics/backend-api pnpm install cd ../../../.. ./tooling/scripts/node-modules-lock.sh lock-all -
Add hooks manually to specific package.json:
{ "scripts": { "preinstall": "../../../tooling/scripts/node-modules-lock.sh unlock", "postinstall": "../../../tooling/scripts/node-modules-lock.sh lock" } }
References
- Lock script:
tooling/scripts/node-modules-lock.sh - Safety rules:
tooling/claude/dot-claude/instructions/safety-rules.md - CLI reference:
infrastructure/CLI_REFERENCE.md(node_modules Locking section) - Root hooks:
package.json(lines 4-5)
Last Updated: 2026-01-13