fix: skip symlinks in makeTreeWritable to prevent EPERM on NixOS/nix-darwin (#1303)
makeTreeWritable used statSync which follows symlinks. On NixOS and nix-darwin, ~/.gsd/agent/bin/ contains symlinks to the immutable Nix store (/run/current-system/sw/bin/). Attempting to chmod those targets crashed GSD on startup with EPERM. Changes: - Use lstatSync instead of statSync — detects symlinks without following them - Skip symlinks entirely (they don't carry own permissions, targets may be immutable) - Added try/catch around chmodSync as safety net for any remaining permission errors on unusual filesystems Secondary analysis: rmSync with force:true already handles symlinks correctly (removes the link, not the target). cpSync with force:true replaces symlinks with regular files (desired behavior for resource sync). Fixes #1298
This commit is contained in:
parent
2a2056bcd7
commit
150575957d
1 changed files with 12 additions and 3 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import { DefaultResourceLoader } from '@gsd/pi-coding-agent'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { homedir } from 'node:os'
|
||||
import { chmodSync, copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs'
|
||||
import { chmodSync, copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs'
|
||||
import { dirname, join, relative, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { compareSemver } from './update-check.js'
|
||||
|
|
@ -133,7 +133,12 @@ export function getNewerManagedResourceVersion(agentDir: string, currentVersion:
|
|||
function makeTreeWritable(dirPath: string): void {
|
||||
if (!existsSync(dirPath)) return
|
||||
|
||||
const stats = statSync(dirPath)
|
||||
// Use lstatSync to avoid following symlinks into immutable filesystems
|
||||
// (e.g., Nix store on NixOS/nix-darwin). Symlinks don't carry their own
|
||||
// permissions and their targets may be read-only by design (#1298).
|
||||
const stats = lstatSync(dirPath)
|
||||
if (stats.isSymbolicLink()) return
|
||||
|
||||
const isDir = stats.isDirectory()
|
||||
const currentMode = stats.mode & 0o777
|
||||
|
||||
|
|
@ -144,7 +149,11 @@ function makeTreeWritable(dirPath: string): void {
|
|||
}
|
||||
|
||||
if (newMode !== currentMode) {
|
||||
chmodSync(dirPath, newMode)
|
||||
try {
|
||||
chmodSync(dirPath, newMode)
|
||||
} catch {
|
||||
// Non-fatal — may fail on read-only filesystems or insufficient permissions
|
||||
}
|
||||
}
|
||||
|
||||
if (isDir) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue