coleam00/Archon · error

Refusing detached run control because process ${String(proce

Error message

Refusing detached run control because process ${String(process.pid)} does not own process group ${String(process.pid)}

What it means

assertDetachedRunProcessOwner() verifies, before enabling detached-run control, that the current process is actually the leader of its own process group (processGroupExists(pid) on POSIX). Detached-run cancellation works by signaling the owner's process group; if the process does not lead that group, signaling would hit the wrong processes, so the runtime refuses. Windows skips this check.

Source

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

  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)}`
    );
  }
}

/** A bounded, user-scoped endpoint: Unix socket on POSIX, named pipe on Windows. */
export function detachedRunControlPath(runId: string): string {
  const token = endpointToken(runId);
  if (process.platform === 'win32') return `\\\\.\\pipe\\archon-workflow-${token}`;
  return join(controlDirectory(), `${token}.sock`);
}

function detachedRunControlLockPath(runId: string): string {
  return join(controlDirectory(), `${endpointToken(runId)}.lock`);
}

function listen(server: Server, path: string): Promise<void> {
  return new Promise((resolve, reject) => {

View on GitHub (pinned to 0773b97458)

Solutions

  1. Launch the detached run through the normal Archon CLI entry point, which sets up its own process group.
  2. If embedding programmatically, ensure the child is started detached with its own process group (e.g. spawn with detached:true on POSIX).
  3. Check how your wrapper (docker exec, tmux, CI runner) starts the process and confirm it allows a new process group.

Example fix

// before
spawn('archon', ['run', ...])               // shares parent's pgroup
// after
spawn('archon', ['run', ...], { detached: true, stdio: 'ignore' }).unref()
Defensive patterns

Strategy: try-catch

Validate before calling

function isGroupLeader(): boolean {
  if (process.platform === 'win32') return true;
  return process.getpid() === process.getpgid(process.getpid());
}
if (!isGroupLeader()) throw new Error('Refusing detached run: not a process-group leader');

Try / catch

try {
  assertDetachedRunProcessOwner();
  await runWorkflowWithOwnedSource(...);
} catch (err) {
  if (err instanceof Error && err.message.includes('does not own process group')) {
    console.error('Launch the CLI detached (own process group), e.g. spawn(..., { detached: true }).');
  } else throw err;
}

Prevention

When it happens

Trigger: Starting a detached run control server (via runWorkflowWithOwnedSource) on POSIX when the process is not a process-group leader — e.g. it was launched in a way that did not call setpgid(0,0).

Common situations: Embedding the Archon CLI inside another process manager, shell job-control setup, or test harness that runs the process in the parent's process group instead of a fresh one; environments where setpgid failed silently at startup.

Related errors


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