fix: skip root-owned jiti cache in deploy rsync (exit 23)#3788
Conversation
The push-to-main deploy aborted at the artifact-sync rsync with exit 23 (`unlink(node_modules/.cache/jiti/nsx-prisma.config.*.mjs) failed: Permission denied (13)` -> `cannot delete non-empty directory: node_modules/.cache/jiti`), before the pm2 restart -- so the new bundle landed on disk and `prisma migrate deploy` ran, but PM2 kept serving the old code. Root cause: `node_modules/.cache/jiti/` (jiti's compiled cache of prisma.config.ts) is owned by root. A manual `pnpm deploy` (which SSHes as root) ran `prisma migrate` and created it root-owned. The GitHub Actions deploy rsync runs as a non-root user and cannot `--delete` a root-owned file inside a root-owned 0755 dir -> exit 23. Add anchored `--exclude='/node_modules/.cache/'` to all three deploy rsyncs (artifact sync + both rollback rsyncs) so --delete never touches the regenerable runtime cache regardless of its owner. Anchored with a leading '/' so it matches only the top-level path, not deep node_modules .cache dirs -- same fix shape as the prior /logs/ anchor fix (#3787). Verified on the production host: `find node_modules -uid 0` returns only .cache and its contents -- no other root-owned path -- so the exclude fully unblocks the deploy. Code-only; production is untouched.
📝 WalkthroughWalkthroughThe deployment workflow is updated to exclude Changesrsync Cache Directory Exclusion
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~3 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
.github/workflows/deploy.yml (1)
347-349: Consider eliminating the root-owned cache to avoid relying on the exclude indefinitely.The exclude correctly unblocks the deploy, but the underlying root-owned
node_modules/.cache/persists. Since deploy-timerun_prisma_cli(Line 270) runs as the non-root Actions user against a root-owned0755directory, jiti can't write new cache entries ifprisma.config.tschanges (it'll silently fall back to no-cache). A one-timesudo chown -R "$SSH_USERNAME" "$remote_dir/node_modules/.cache"on the host (or removing the directory so it regenerates under the correct owner) would restore writable caching and remove the latent ownership skew, while keeping the exclude as a safety net.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/deploy.yml around lines 347 - 349, Add a step that removes or re-owns the root-owned cache on the remote host before syncing so the non-root Actions user can write jiti/prisma cache: SSH to the remote and run either a one-time sudo chown -R "$SSH_USERNAME" "$remote_dir/node_modules/.cache" or rm -rf "$remote_dir/node_modules/.cache" (so it regenerates owned by the Actions user) prior to the rsync/--delete step (the line that currently calls rsync ... || fail_deployment). Keep the existing --exclude='/node_modules/.cache/' as a safety net after fixing ownership.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In @.github/workflows/deploy.yml:
- Around line 347-349: Add a step that removes or re-owns the root-owned cache
on the remote host before syncing so the non-root Actions user can write
jiti/prisma cache: SSH to the remote and run either a one-time sudo chown -R
"$SSH_USERNAME" "$remote_dir/node_modules/.cache" or rm -rf
"$remote_dir/node_modules/.cache" (so it regenerates owned by the Actions user)
prior to the rsync/--delete step (the line that currently calls rsync ... ||
fail_deployment). Keep the existing --exclude='/node_modules/.cache/' as a
safety net after fixing ownership.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 5907c343-e6cb-4cc1-9d0b-61d9cdd45778
📒 Files selected for processing (1)
.github/workflows/deploy.yml
Problem
The push-to-
maindeploy aborts at the artifact-syncrsyncwith exit 23, beforepm2 restart— so the new bundle is synced to disk andprisma migrate deployruns, but PM2 keeps serving the old code (same "silent failure" shape as #3787, different cause).Runtime error from the failed run (
Extract and restart serverstep):Root cause
node_modules/.cache/jiti/is jiti's compiled cache ofprisma.config.ts(Prisma 7 loads its.tsconfig through jiti, which writes content-hashed output there). On the production host it is owned byroot:A manual
pnpm deploy(scripts/deploy, which SSHes in as root per the maintainer's ssh config) runsprisma migrateand creates the cache root-owned. The GitHub Actions deploy rsync runs as a non-root user — it cannotunlinka root-owned file inside a root-owned0755directory →--deletefails → exit 23.Fix
Add an anchored
--exclude='/node_modules/.cache/'to all three deploy rsyncs (artifact sync + both rollback rsyncs):--deletenow never touches the jiti cache — a regenerable runtime artifact that has no business being pruned by a deploy — so ownership is irrelevant. Anchored with a leading/so it matches only the top-levelnode_modules/.cache, not deepnode_modules/**/.cachedirs (same anchoring discipline as the/logs/fix in #3787).Verification (production host, rsync 3.2.7)
find /home/deploy/nsx/node_modules -uid 0returns only.cacheand its contents — zero other root-owned paths. So this single exclude fully unblocks the deploy; there is no second root-owned path waiting to fail the next run.@sentry"cannot delete" errors this time — the deploy got all the way past the nested-node_modules pruning and stopped only at the.cachewall.Production state (no regression)
The failed deploy aborted at the rsync, before
pm2 restartand beforedeployment_applied=true, so it neither restarted PM2 nor rolled back. Production is healthy:post_list→ HTTP 200, PM2serveronline with ~16h uptime (= untouched by the failed deploy). This PR makes the next deploy complete throughpm2 restart.Notes
.cache/jitistays on disk. Whileprisma.config.tsis unchanged (hash88e0c7e4), jiti only reads the world-readable cache — fine. Ifprisma.config.tsever changes, jiti must write a new hash file into the root-owned dir as the non-root deploy user, which may fail. A one-timerm -rf node_modules/.cacheon the host (as root) would clear that, but it is a production mutation and out of scope for this CI-only fix.scripts/deploy(manual path) is unaffected — its rsyncs use neither--deletenor these excludes and never syncnode_modules.Risk
Low — CI-workflow change only.
Summary by CodeRabbit