Fix read-only file permissions after cpSync from Nix store

Co-authored-by: faldor20 <26968035+faldor20@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-03-17 01:45:38 +00:00 committed by Lex Christopherson
parent 4e562831e6
commit 9ed5c12efe

View file

@ -1,6 +1,6 @@
import { DefaultResourceLoader } from '@gsd/pi-coding-agent'
import { homedir } from 'node:os'
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
import { chmodSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
import { dirname, join, relative, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { compareSemver } from './update-check.js'
@ -133,6 +133,26 @@ export function getNewerManagedResourceVersion(agentDir: string, currentVersion:
return compareSemver(managedVersion, currentVersion) > 0 ? managedVersion : null
}
/**
* Recursively makes all files and directories under dirPath owner-writable.
*
* Files copied from the Nix store inherit read-only modes (0444/0555).
* Calling this before cpSync prevents overwrite failures on subsequent upgrades,
* and calling it after ensures the next run can overwrite the copies too.
*/
function makeTreeWritable(dirPath: string): void {
if (!existsSync(dirPath)) return
chmodSync(dirPath, 0o755)
for (const entry of readdirSync(dirPath, { withFileTypes: true })) {
const entryPath = join(dirPath, entry.name)
if (entry.isDirectory()) {
makeTreeWritable(entryPath)
} else {
chmodSync(entryPath, 0o644)
}
}
}
/**
* Syncs all bundled resources to agentDir (~/.gsd/agent/) on every launch.
*
@ -155,6 +175,7 @@ export function initResources(agentDir: string): void {
// then overwrite so updates land on next launch. Only bundled subdirs are removed;
// user-created extension directories are preserved.
const destExtensions = join(agentDir, 'extensions')
makeTreeWritable(destExtensions)
for (const entry of readdirSync(bundledExtensionsDir, { withFileTypes: true })) {
if (entry.isDirectory()) {
const target = join(destExtensions, entry.name)
@ -162,11 +183,13 @@ export function initResources(agentDir: string): void {
}
}
cpSync(bundledExtensionsDir, destExtensions, { recursive: true, force: true })
makeTreeWritable(destExtensions)
// Sync agents
const destAgents = join(agentDir, 'agents')
const srcAgents = join(resourcesDir, 'agents')
if (existsSync(srcAgents)) {
makeTreeWritable(destAgents)
for (const entry of readdirSync(srcAgents, { withFileTypes: true })) {
if (entry.isDirectory()) {
const target = join(destAgents, entry.name)
@ -174,12 +197,14 @@ export function initResources(agentDir: string): void {
}
}
cpSync(srcAgents, destAgents, { recursive: true, force: true })
makeTreeWritable(destAgents)
}
// Sync skills
const destSkills = join(agentDir, 'skills')
const srcSkills = join(resourcesDir, 'skills')
if (existsSync(srcSkills)) {
makeTreeWritable(destSkills)
for (const entry of readdirSync(srcSkills, { withFileTypes: true })) {
if (entry.isDirectory()) {
const target = join(destSkills, entry.name)
@ -187,6 +212,7 @@ export function initResources(agentDir: string): void {
}
}
cpSync(srcSkills, destSkills, { recursive: true, force: true })
makeTreeWritable(destSkills)
}
writeManagedResourceManifest(agentDir)