different-ai/openwork · error

invalid-staging

invalid-staging

Error message

Staging directory must be absolute.

What it means

downloadBinaryToPath downloads a binary through a bounded fetch pipeline and stages it in options.stagingDir before moving it into place. As part of input hardening it requires stagingDir to be a non-empty, length-bounded, ABSOLUTE path; a relative path throws a transferError with code invalid-staging.

Source

Thrown at apps/desktop/electron/binary-transfer.mjs:271

// Downloads stream into a private staging file inside an app-owned directory
// first, and only a fully successful download is placed into the workspace.
// The staging directory sits outside every authorized workspace root, so a
// process with workspace write access cannot swap its parents; its cleanup is
// the only path-based removal this module performs. Inside the workspace the
// destination is created exclusively, verified by device and inode, written
// through the verified handle, and never unlinked by path: failure cleanup
// truncates through the handle instead.
export async function downloadBinaryToPath(input, options) {
  const url = remoteUrl(input?.url, options?.allowedUrlPrefixes);
  const destination = await resolveAuthorizedPath(input?.destinationPath, options?.authorizedRoots);
  const destinationPath = destination.path;
  const maxBytes = boundedByteCount(input?.maxBytes, "Maximum download size", { allowUndefined: true });
  const headers = boundedRecord(input?.headers, "Headers", MAX_HEADERS);
  const method = boundedString(input?.method ?? "GET", "Method", { required: true, maxLength: 16 });
  const signal = timeoutSignal(input?.timeoutMs, options?.signal);
  const stagingDir = boundedString(options?.stagingDir, "Staging directory", { required: true, maxLength: 32_768 });
  if (!path.isAbsolute(stagingDir)) {
    throw transferError("Staging directory must be absolute.", "invalid-staging");
  }
  let stagingPath;
  let stagingFile;
  let destinationFile;
  try {
    const response = await options.fetcher(url, {
      method,
      headers,
      credentials: "omit",
      cache: "no-store",
      // The endpoint allowlist covers only the initial URL, so a redirect
      // must never be followed to an unvalidated destination.
      redirect: "error",
      signal,
    });
    if (!response.ok) {
      return { ...responseMetadata(response), body: await responseText(response), path: null, bytes: 0 };
    }

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Resolve the staging directory to an absolute path with path.resolve()/path.join(os.tmpdir(), ...) before calling.
  2. Store absolute paths in configuration, or resolve them at call time against the app's data directory.
  3. Ensure the directory also exists (and is writable) — create it with fs.mkdir(recursive: true) beforehand.
  4. Check the options object is being passed with the right key (options.stagingDir, not input).

Example fix

// before
await downloadBinaryToPath(url, dest, { stagingDir: "tmp/staging" })
// after
import path from "node:path";
await downloadBinaryToPath(url, dest, { stagingDir: path.resolve(appDataDir, "staging") })
Defensive patterns

Strategy: validation

Validate before calling

import path from "node:path";
const stagingDir = path.resolve(candidateDir);
if (!path.isAbsolute(stagingDir)) throw new Error('stagingDir must be absolute');
await fs.mkdir(stagingDir, { recursive: true });

Type guard

function isAbsoluteDir(p: unknown): p is string { return typeof p === 'string' && p.length > 0 && path.isAbsolute(p) }

Try / catch

try { await downloadBinaryToPath(url, dest, { stagingDir }) } catch (e) { if (e.code === 'invalid-staging') console.error('stagingDir must be absolute, got:', stagingDir); }

Prevention

When it happens

Trigger: Calling downloadBinaryToPath with options.stagingDir set to a relative path such as "tmp/staging" or ".cache" instead of an absolute filesystem path.

Common situations: Reconstructing the staging path with a relative CWD assumption; config files storing relative paths; tests running with a different working directory than the caller expected; platform code assuming forward-slash relative temp dirs.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/7e01b8fe9ecb833d. Report an issue: GitHub.