{"record":{"id":"59c7b5b33949247b","repo":"paperclipai/paperclip","slug":"refusing-to-write-service-definition-through-unsaf","errorCode":null,"errorMessage":"Refusing to write service definition through unsafe directory ${directoryPath}.","messagePattern":"Refusing to write service definition through unsafe directory (.+?)\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/src/services/service-manager.ts","lineNumber":142,"sourceCode":"    <key>PAPERCLIP_INSTANCE_ID</key><string>${escapeXml(input.instanceId)}</string>\n    <key>PAPERCLIP_HOME</key><string>${escapeXml(input.homeDir)}</string>\n  </dict>\n  <key>RunAtLoad</key><true/>\n  <key>KeepAlive</key><true/>\n  <key>ThrottleInterval</key><integer>5</integer>\n  <key>ExitTimeOut</key><integer>300</integer>\n  <key>StandardOutPath</key><string>${escapeXml(input.stdoutPath)}</string>\n  <key>StandardErrorPath</key><string>${escapeXml(input.stderrPath)}</string>\n</dict>\n</plist>\n`;\n}\n\nasync function writeIfChanged(filePath: string, contents: string): Promise<boolean> {\n  const directoryPath = path.dirname(filePath);\n  await fs.mkdir(directoryPath, { recursive: true, mode: 0o700 });\n  const directoryStat = await fs.lstat(directoryPath);\n  if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) throw new Error(`Refusing to write service definition through unsafe directory ${directoryPath}.`);\n  const currentUid = process.getuid?.();\n  if (currentUid !== undefined && directoryStat.uid !== currentUid) throw new Error(`Refusing to write service definition in directory not owned by the current user: ${directoryPath}.`);\n  try {\n    const stat = await fs.lstat(filePath);\n    if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink > 1) throw new Error(`Refusing to replace unsafe service definition ${filePath}.`);\n    if (currentUid !== undefined && stat.uid !== currentUid) throw new Error(`Refusing to replace service definition not owned by the current user: ${filePath}.`);\n    if (await fs.readFile(filePath, \"utf8\") === contents) return false;\n  } catch (error) {\n    if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n  }\n  const temporaryPath = path.join(directoryPath, `.${path.basename(filePath)}.tmp-${process.pid}-${Date.now()}`);\n  try {\n    await fs.writeFile(temporaryPath, contents, { encoding: \"utf8\", mode: 0o644, flag: \"wx\" });\n    await fs.rename(temporaryPath, filePath);\n  } finally {\n    await fs.rm(temporaryPath, { force: true });\n  }\n  return true;","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/paperclipai/paperclip/blob/120ae5428fa29bee300bcf806491cd4d965fbb7c/cli/src/services/service-manager.ts#L124-L160","documentation":"writeIfChanged checks the directory that will hold the service definition and found it (after mkdir) is not a plain directory, e.g. resolves through a symlink; writing there is refused as unsafe.","triggerScenarios":"Thrown at cli/src/services/service-manager.ts:142 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Use a safe, non-symlink directory for the service definition: ${directoryPath}."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"120ae5428fa29bee300bcf806491cd4d965fbb7c","analyzedAt":"2026-08-18T22:49:45.177Z","contentChangedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}