paperclipai/paperclip · error · Error

Refusing to write service definition in directory not owned

Error message

Refusing to write service definition in directory not owned by the current user: ${directoryPath}.

What it means

Thrown by writeIfChanged when the service definition directory's owner UID does not match the current process's UID (process.getuid()). Writing a systemd/launchd service definition into a directory owned by another user could allow privilege escalation if that user controls the directory contents. This check is skipped on platforms where process.getuid is undefined (e.g., Windows).

Source

Thrown at cli/src/services/service-manager.ts:144

  </dict>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
  <key>ThrottleInterval</key><integer>5</integer>
  <key>ExitTimeOut</key><integer>300</integer>
  <key>StandardOutPath</key><string>${escapeXml(input.stdoutPath)}</string>
  <key>StandardErrorPath</key><string>${escapeXml(input.stderrPath)}</string>
</dict>
</plist>
`;
}

async function writeIfChanged(filePath: string, contents: string): Promise<boolean> {
  const directoryPath = path.dirname(filePath);
  await fs.mkdir(directoryPath, { recursive: true, mode: 0o700 });
  const directoryStat = await fs.lstat(directoryPath);
  if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) throw new Error(`Refusing to write service definition through unsafe directory ${directoryPath}.`);
  const currentUid = process.getuid?.();
  if (currentUid !== undefined && directoryStat.uid !== currentUid) throw new Error(`Refusing to write service definition in directory not owned by the current user: ${directoryPath}.`);
  try {
    const stat = await fs.lstat(filePath);
    if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink > 1) throw new Error(`Refusing to replace unsafe service definition ${filePath}.`);
    if (currentUid !== undefined && stat.uid !== currentUid) throw new Error(`Refusing to replace service definition not owned by the current user: ${filePath}.`);
    if (await fs.readFile(filePath, "utf8") === contents) return false;
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
  }
  const temporaryPath = path.join(directoryPath, `.${path.basename(filePath)}.tmp-${process.pid}-${Date.now()}`);
  try {
    await fs.writeFile(temporaryPath, contents, { encoding: "utf8", mode: 0o644, flag: "wx" });
    await fs.rename(temporaryPath, filePath);
  } finally {
    await fs.rm(temporaryPath, { force: true });
  }
  return true;
}

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Check ownership: 'ls -la ~/.config/systemd/user/' or 'ls -la ~/Library/LaunchAgents/' and compare the owner UID to your current UID ('id -u').
  2. Fix ownership: 'chown -R $(id -u):$(id -g) ~/.config/systemd/user/'.
  3. If you previously ran with sudo, re-run without sudo, or fix ownership of the affected directories.
  4. Verify you are running as the correct user account for this Paperclip instance.

Example fix

// before: directory owned by root
// ls -la ~/.config/systemd/user/ -> owner: root

// after: fix ownership to current user
// sudo chown -R $(id -u):$(id -g) ~/.config/systemd/user/
// chmod 700 ~/.config/systemd/user/
Defensive patterns

Strategy: validation

Validate before calling

import fs from 'node:fs';
import path from 'node:path';

function isDirOwnedByCurrentUser(dirPath: string): boolean {
  const currentUid = process.getuid?.();
  if (currentUid === undefined) return true; // skip on platforms without getuid
  try {
    const stat = fs.lstatSync(dirPath);
    return stat.isDirectory() && !stat.isSymbolicLink() && stat.uid === currentUid;
  } catch {
    return false;
  }
}

// Call before service install:
const serviceDir = path.dirname(definitionPath);
if (!isDirOwnedByCurrentUser(serviceDir)) {
  throw new Error(`Run 'chown -R $(id -u):$(id -g) ${serviceDir}' and retry.`);
}

Try / catch

try {
  await manager.install({ startNow: true, startOnLogin: true });
} catch (error) {
  if (error instanceof Error && error.message.includes('not owned by the current user')) {
    console.error('Fix directory ownership: sudo chown -R $(id -u):$(id -g)', path.dirname(manager.definitionPath));
  }
  throw error;
}

Prevention

When it happens

Trigger: Calling install/start/restart on a service manager when ~/.config/systemd/user/ or ~/Library/LaunchAgents/ is owned by a different user (e.g., root or another account). This commonly occurs when running as a different user than the one that created the directory, or after a user migration.

Common situations: Running paperclipai service commands with sudo (which may set ownership to root). The service directory was created by a different user account. Home directory ownership was changed (e.g., chown -R root ~). Running inside a container where UID mapping differs from the host.

Related errors


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/1f8b691beab89a7b. Report an issue: GitHub.