Merge pull request #4172 from MoerAI/fix/team-mode-base-dir-chmod-eperm
This commit is contained in:
@@ -117,4 +117,53 @@ describe("paths", () => {
|
|||||||
expect(directoryStat.mode & 0o777).toBe(0o700)
|
expect(directoryStat.mode & 0o777).toBe(0o700)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("ensureBaseDirs swallows EPERM from chmod and logs a warning instead of aborting team-mode init", async () => {
|
||||||
|
// given: directories exist with permissive mode that chmod cannot tighten
|
||||||
|
// (mirrors macOS network mount / non-owner / SIP cases reported in #4023).
|
||||||
|
const baseDir = path.join(tmpdir(), `omo-test-eperm-${randomUUID()}`)
|
||||||
|
temporaryDirectories.push(baseDir)
|
||||||
|
await mkdir(baseDir, { recursive: true })
|
||||||
|
await mkdir(path.join(baseDir, "teams"), { recursive: true })
|
||||||
|
await mkdir(path.join(baseDir, "runtime"), { recursive: true })
|
||||||
|
await mkdir(path.join(baseDir, "worktrees"), { recursive: true })
|
||||||
|
|
||||||
|
const realFs = await import("node:fs/promises")
|
||||||
|
let chmodCalls = 0
|
||||||
|
mock.module("node:fs/promises", () => ({
|
||||||
|
...realFs,
|
||||||
|
chmod: async (target: string) => {
|
||||||
|
chmodCalls += 1
|
||||||
|
const eperm = Object.assign(new Error(`EPERM: operation not permitted, chmod '${target}'`), {
|
||||||
|
code: "EPERM",
|
||||||
|
syscall: "chmod",
|
||||||
|
path: target,
|
||||||
|
errno: -1,
|
||||||
|
})
|
||||||
|
throw eperm
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { ensureBaseDirs: ensureBaseDirsWithMockedChmod } = await import("./paths")
|
||||||
|
logCalls.splice(0)
|
||||||
|
|
||||||
|
// when
|
||||||
|
let thrown: unknown = null
|
||||||
|
try {
|
||||||
|
await ensureBaseDirsWithMockedChmod(baseDir)
|
||||||
|
} catch (error) {
|
||||||
|
thrown = error
|
||||||
|
}
|
||||||
|
|
||||||
|
// then: function does not throw, EPERM was reached, and one warning was logged.
|
||||||
|
expect(thrown).toBeNull()
|
||||||
|
expect(chmodCalls).toBeGreaterThan(0)
|
||||||
|
const warnings = logCalls.filter(([message]) =>
|
||||||
|
message === "team-mode: chmod refused on base directory; continuing with existing permissions"
|
||||||
|
)
|
||||||
|
expect(warnings.length).toBeGreaterThan(0)
|
||||||
|
const firstWarning = warnings[0]?.[1] as { code?: string; path?: string } | undefined
|
||||||
|
expect(firstWarning?.code).toBe("EPERM")
|
||||||
|
expect(firstWarning?.path).toContain(baseDir)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -100,6 +100,23 @@ export async function discoverTeamSpecs(
|
|||||||
return discoveredTeamSpecs
|
return discoveredTeamSpecs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function safeChmod(directoryPath: string, mode: number): Promise<void> {
|
||||||
|
try {
|
||||||
|
await chmod(directoryPath, mode)
|
||||||
|
} catch (error) {
|
||||||
|
const errnoError = error as NodeJS.ErrnoException
|
||||||
|
if (errnoError?.code === "EPERM" || errnoError?.code === "ENOTSUP" || errnoError?.code === "EINVAL") {
|
||||||
|
log("team-mode: chmod refused on base directory; continuing with existing permissions", {
|
||||||
|
path: directoryPath,
|
||||||
|
code: errnoError.code,
|
||||||
|
syscall: errnoError.syscall,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function ensureBaseDirs(baseDir: string): Promise<void> {
|
export async function ensureBaseDirs(baseDir: string): Promise<void> {
|
||||||
const directories = [
|
const directories = [
|
||||||
baseDir,
|
baseDir,
|
||||||
@@ -110,13 +127,13 @@ export async function ensureBaseDirs(baseDir: string): Promise<void> {
|
|||||||
|
|
||||||
for (const directoryPath of directories) {
|
for (const directoryPath of directories) {
|
||||||
await mkdir(directoryPath, { recursive: true, mode: 0o700 })
|
await mkdir(directoryPath, { recursive: true, mode: 0o700 })
|
||||||
await chmod(directoryPath, 0o700)
|
await safeChmod(directoryPath, 0o700)
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(directories.map(async (directoryPath) => {
|
await Promise.all(directories.map(async (directoryPath) => {
|
||||||
const directoryStat = await stat(directoryPath)
|
const directoryStat = await stat(directoryPath)
|
||||||
if ((directoryStat.mode & 0o777) !== 0o700) {
|
if ((directoryStat.mode & 0o777) !== 0o700) {
|
||||||
await chmod(directoryPath, 0o700)
|
await safeChmod(directoryPath, 0o700)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user