different-ai/openwork · error · ApiError

invalid_payload

invalid_payload

Error message

No authorized workspace roots are available.

What it means

resolveAuthorizedFile resolves a requested path for direct cloud upload against the server's authorized workspace roots. If no roots are configured, there is nothing a request could be authorized against, so it throws a 400 invalid_payload ApiError.

Source

Thrown at apps/server/src/extensions/cloud-uploads.ts:114

  for (const workspace of config.workspaces) pushUniqueResolvedPath(roots, workspace.path);
  for (const root of config.authorizedRoots) pushUniqueResolvedPath(roots, root);
  return roots;
}

function searchRoots(config: ServerConfig, context: Record<string, unknown>, roots: string[]) {
  const candidates: string[] = [];
  const directory = readString(context, "directory");
  const worktree = readString(context, "worktree");
  if (directory) pushUniqueResolvedPath(candidates, directory);
  if (worktree) pushUniqueResolvedPath(candidates, worktree);
  for (const workspace of config.workspaces) pushUniqueResolvedPath(candidates, workspace.path);
  for (const root of roots) pushUniqueResolvedPath(candidates, root);
  return candidates.filter((candidate) => roots.some((root) => isWithinRoot(candidate, root)));
}

async function resolveAuthorizedFile(config: ServerConfig, context: Record<string, unknown>, requested: string) {
  const roots = allowedRoots(config);
  if (!roots.length) throw new ApiError(400, "invalid_payload", "No authorized workspace roots are available.");
  const realRoots: string[] = [];
  for (const root of roots) {
    try {
      pushUniqueResolvedPath(realRoots, await realpath(root));
    } catch (error) {
      if (!isRecord(error) || error.code !== "ENOENT") throw error;
    }
  }
  const candidates = isAbsolute(requested)
    ? [resolve(requested)]
    : searchRoots(config, context, roots).map((root) => resolve(root, requested));
  for (const candidate of candidates) {
    if (!roots.some((root) => isWithinRoot(candidate, root))) continue;
    try {
      const realCandidate = await realpath(candidate);
      if (!realRoots.some((root) => isWithinRoot(realCandidate, root))) continue;
      const info = await stat(realCandidate);
      if (!info.isFile()) continue;

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Configure at least one authorized workspace root in the server config and restart
  2. Verify the config file's workspace/root settings are being loaded (check for typos in keys or wrong config path)
  3. Confirm allowedRoots() is not filtering out existing roots (e.g. non-existent directories being dropped)

Example fix

// before
{ } // no workspace configured
// after
{ "workspaceRoot": "/Users/me/projects" }
Defensive patterns

Strategy: validation

Validate before calling

if (allowedRoots(config).length === 0) return badRequest("no workspace roots");

Try / catch

try { await uploadCloud(p); } catch (e) { if (e.code === "invalid_payload") { openWorkspaceSettings(); } else throw e; }

Prevention

When it happens

Trigger: Calling the cloud-upload endpoint when the server config yields an empty allowedRoots() list — no workspace roots were configured or all were filtered out.

Common situations: Server started without a workspace directory configured; workspace root path removed from config; misconfigured config file resulting in an empty roots array.

Related errors


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