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
- Resolve the staging directory to an absolute path with path.resolve()/path.join(os.tmpdir(), ...) before calling.
- Store absolute paths in configuration, or resolve them at call time against the app's data directory.
- Ensure the directory also exists (and is writable) — create it with fs.mkdir(recursive: true) beforehand.
- 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
- Always build staging paths with path.resolve/path.join against a known absolute base
- Never store relative staging paths in configuration
- Create the staging directory before downloading
- Pass stagingDir in options, not the input object
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
- ${result}
- Failed to write .opencode/openwork.json
- Environment variable store could not be read
- Environment variable store is invalid JSON
- path_escape
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/7e01b8fe9ecb833d.
Report an issue: GitHub.