coleam00/Archon · critical

Detached run control path is not a directory: ${directory}

Error message

Detached run control path is not a directory: ${directory}

What it means

controlDirectory() creates the detached-run control directory (e.g. /tmp/archon-<uid>) and then hardens it: on POSIX it lstats the path and requires a real directory (not a symlink) owned by the current user with mode 0700. This error fires when the path exists but is not a regular directory — typically a symlink or file planted at the expected location. It prevents a local attacker from redirecting control files through a symlink.

Source

Thrown at packages/cli/src/utils/detached-run-control.ts:62

    this.name = 'DetachedRunOwnerUnavailableError';
  }
}

function endpointToken(runId: string): string {
  return createHash('sha256').update(runId).digest('hex').slice(0, 32);
}

function controlDirectory(): string {
  const uid = process.getuid?.();
  const directory =
    process.platform === 'win32'
      ? join(tmpdir(), 'archon-run-control')
      : `/tmp/archon-${uid === undefined ? 'user' : String(uid)}`;
  mkdirSync(directory, { recursive: true, mode: 0o700 });
  if (process.platform !== 'win32') {
    const stat = lstatSync(directory);
    if (!stat.isDirectory() || stat.isSymbolicLink()) {
      throw new Error(`Detached run control path is not a directory: ${directory}`);
    }
    if (uid !== undefined && stat.uid !== uid) {
      throw new Error(`Detached run control directory is owned by another user: ${directory}`);
    }
    if ((stat.mode & 0o077) !== 0) {
      throw new Error(`Detached run control directory must have mode 0700: ${directory}`);
    }
  }
  return directory;
}

/** Prove the marked POSIX owner has the process group that active cancellation will signal. */
export function assertDetachedRunProcessOwner(): void {
  if (process.platform !== 'win32' && !processGroupExists(process.pid)) {
    throw new Error(
      `Refusing detached run control because process ${String(process.pid)} does not own process group ${String(process.pid)}`
    );
  }

View on GitHub (pinned to 0773b97458)

Solutions

  1. Inspect the path: ls -la /tmp/archon-* and confirm what it actually is.
  2. Remove the file or symlink (it is a temp path, safe to delete when no run is active): rm /tmp/archon-<uid>, then let Archon recreate it.
  3. Ensure nothing in your environment (mounts, provisioning scripts) pre-creates that path as a non-directory.

Example fix

// before: path is a symlink
lrwxrwxrwx archon-1000 -> /somewhere
// after
rm /tmp/archon-1000   # Archon recreates it as a 0700 directory
Defensive patterns

Strategy: try-catch

Validate before calling

import { lstatSync } from 'node:fs';
function controlDirLooksSafe(dir: string): boolean {
  try { const st = lstatSync(dir); return st.isDirectory() && !st.isSymbolicLink(); }
  catch { return true; } // absent is fine; Archon will create it
}

Type guard

function isRealDirectory(st: import('node:fs').Stats): boolean {
  return st.isDirectory() && !st.isSymbolicLink();
}

Try / catch

try {
  const dir = controlDirectory();
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Detached run control path is not a directory')) {
    rmSync(expectedControlDir, { force: true, recursive: true });
    // retry once
  } else throw err;
}

Prevention

When it happens

Trigger: lstatSync(directory) shows isDirectory() false or isSymbolicLink() true for the control directory path when a detached-run control path or lock path is resolved.

Common situations: Someone or something replaced /tmp/archon-<uid> with a symlink (often to another tmpfs or to attack the runtime); a file named archon-<uid> exists in /tmp from a misconfigured setup; a container volume mounts a file at that path.

Related errors


AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01). Data as JSON: /api/errors/8e7aea444069885e. Report an issue: GitHub.