paperclipai/paperclip · error

OpenCode ${QUALIFIED_OPENCODE_VERSION} runner-owned executab

Error message

OpenCode ${QUALIFIED_OPENCODE_VERSION} runner-owned executable binding is unavailable; refusing ambient PATH or PAPERCLIP_OPENCODE_COMMAND fallback

What it means

trustedOpenCodeLaunchBinding only accepts the executable binding injected by runnerd (an inherited /proc/self/fd descriptor on Linux, or a verified mac executable snapshot). On macOS it validates the path with lstat: a regular, non-symlink file owned by the current uid, mode 0500, nlink 1, inside a 0700 non-symlink directory named .paperclip-verified-executable-<hash>. If that strict snapshot fails, the proxy refuses any ambient PATH lookup or PAPERCLIP_OPENCODE_COMMAND fallback and fails closed.

Source

Thrown at packages/paperclip-runner/src/cli/opencode-proxy-command.ts:78

      const currentUid = process.getuid?.();
      snapshotIsValid =
        metadata.isFile() &&
        !metadata.isSymbolicLink() &&
        metadata.nlink === 1 &&
        (metadata.mode & 0o777) === 0o500 &&
        directoryMetadata.isDirectory() &&
        !directoryMetadata.isSymbolicLink() &&
        (directoryMetadata.mode & 0o777) === 0o700 &&
        currentUid !== undefined &&
        metadata.uid === currentUid &&
        directoryMetadata.uid === currentUid &&
        (expected === undefined ||
          (metadata.dev === expected.dev && metadata.ino === expected.ino));
    } catch {
      snapshotIsValid = false;
    }
    if (!snapshotIsValid) {
      throw new Error(
        `OpenCode ${QUALIFIED_OPENCODE_VERSION} runner-owned executable binding is unavailable; refusing ambient PATH or PAPERCLIP_OPENCODE_COMMAND fallback`,
      );
    }
    return metadata!;
  };
  if (
    process.platform === "darwin" &&
    isAbsolute(command) &&
    basename(command) === "launch" &&
    /^\.paperclip-verified-executable-[0-9a-f]{32}$/.test(
      basename(dirname(command)),
    )
  ) {
    const initialMetadata = validateMacSnapshot();
    let sourceFd: number;
    try {
      sourceFd = openSync(command, "r");
    } catch {

View on GitHub (pinned to 5716fe907e)

Solutions

  1. Re-run through runnerd so it re-materializes a fresh runner-owned verified executable binding for this launch
  2. Do not invoke the proxy directly with a stale path; always pass the current --paperclip-trusted-opencode-executable argument runnerd provides
  3. Ensure the same uid runs the proxy as the one that materialized the binding
  4. Do not set PAPERCLIP_OPENCODE_COMMAND expecting a fallback — it is deliberately ignored
  5. Check that no cleanup process removed .paperclip-verified-executable-* directories mid-run

Example fix

// before (second launch reusing consumed path)
const binding = trustedOpenCodeLaunchBinding([TRUSTED_OPENCODE_EXECUTABLE_ARG, staleMacPath]);
// after (get a fresh binding from runnerd)
const freshPath = await runnerd.materializeVerifiedOpenCodeExecutable();
const binding = trustedOpenCodeLaunchBinding([TRUSTED_OPENCODE_EXECUTABLE_ARG, freshPath]);
Defensive patterns

Strategy: validation

Validate before calling

import { lstatSync } from "node:fs";
function canUseTrustedBinding(command) {
  if (process.platform !== "darwin") return true;
  try {
    const st = lstatSync(command);
    const dir = lstatSync(require("node:path").dirname(command));
    const uid = process.getuid?.();
    return st.isFile() && !st.isSymbolicLink() && st.nlink === 1 &&
      (st.mode & 0o777) === 0o500 &&
      dir.isDirectory() && !dir.isSymbolicLink() &&
      (dir.mode & 0o777) === 0o700 &&
      uid !== undefined && st.uid === uid && dir.uid === uid;
  } catch {
    return false;
  }
}
if (!canUseTrustedBinding(args[1])) {
  await runnerd.materializeVerifiedOpenCodeExecutable(); // re-bind before launching
}

Type guard

function isValidVerifiedExecutablePath(command) {
  return process.platform === "darwin" &&
    typeof command === "string" && require("node:path").isAbsolute(command) &&
    require("node:path").basename(command) === "launch" &&
    /^\.paperclip-verified-executable-[0-9a-f]{32}$/.test(require("node:path").basename(require("node:path").dirname(command)));
}

Try / catch

try {
  const binding = trustedOpenCodeLaunchBinding(args);
  spawnViaBinding(binding);
} catch (error) {
  if (error instanceof Error && error.message.includes("runner-owned executable binding is unavailable")) {
    // binding consumed or invalid; request a fresh one from runnerd
    const fresh = await runnerd.materializeVerifiedOpenCodeExecutable();
    spawnViaBinding(trustedOpenCodeLaunchBinding([TRUSTED_OPENCODE_EXECUTABLE_ARG, fresh]));
    return;
  }
  throw error;
}

Prevention

When it happens

Trigger: On darwin, validateMacSnapshot() fails: the verified path was already consumed/deleted (unlinkSync after a prior launch), the directory or file permissions/ownership changed, the path is a symlink, nlink != 1, or lstat throws because the file is gone. Reached via materializeForSpawn/afterSpawn when the verified file was unlinked after initial validation.

Common situations: Launching OpenCode twice — the first launch unlinks the verified executable, so the second launch finds nothing; running the proxy as a different user than runnerd; an antivirus/cleanup process deleting the temp verified dir; filesystem that doesn't honor the expected modes; manually invoking the proxy without the runnerd-injected argument.

Related errors


AI-assisted analysis of paperclipai/paperclip@5716fe907e (2026-09-02). Data as JSON: /api/errors/2931d6e51e0249c1. Report an issue: GitHub.