{"record":{"id":"7fc08ddd7a149a1a","repo":"different-ai/openwork","slug":"invalid-payload-7fc08d","errorCode":"invalid_payload","errorMessage":"No authorized workspace roots are available.","messagePattern":"No authorized workspace roots are available\\.","errorType":"http","errorClass":"ApiError","httpStatus":400,"severity":"error","filePath":"apps/server/src/extensions/cloud-uploads.ts","lineNumber":114,"sourceCode":"  for (const workspace of config.workspaces) pushUniqueResolvedPath(roots, workspace.path);\n  for (const root of config.authorizedRoots) pushUniqueResolvedPath(roots, root);\n  return roots;\n}\n\nfunction searchRoots(config: ServerConfig, context: Record<string, unknown>, roots: string[]) {\n  const candidates: string[] = [];\n  const directory = readString(context, \"directory\");\n  const worktree = readString(context, \"worktree\");\n  if (directory) pushUniqueResolvedPath(candidates, directory);\n  if (worktree) pushUniqueResolvedPath(candidates, worktree);\n  for (const workspace of config.workspaces) pushUniqueResolvedPath(candidates, workspace.path);\n  for (const root of roots) pushUniqueResolvedPath(candidates, root);\n  return candidates.filter((candidate) => roots.some((root) => isWithinRoot(candidate, root)));\n}\n\nasync function resolveAuthorizedFile(config: ServerConfig, context: Record<string, unknown>, requested: string) {\n  const roots = allowedRoots(config);\n  if (!roots.length) throw new ApiError(400, \"invalid_payload\", \"No authorized workspace roots are available.\");\n  const realRoots: string[] = [];\n  for (const root of roots) {\n    try {\n      pushUniqueResolvedPath(realRoots, await realpath(root));\n    } catch (error) {\n      if (!isRecord(error) || error.code !== \"ENOENT\") throw error;\n    }\n  }\n  const candidates = isAbsolute(requested)\n    ? [resolve(requested)]\n    : searchRoots(config, context, roots).map((root) => resolve(root, requested));\n  for (const candidate of candidates) {\n    if (!roots.some((root) => isWithinRoot(candidate, root))) continue;\n    try {\n      const realCandidate = await realpath(candidate);\n      if (!realRoots.some((root) => isWithinRoot(realCandidate, root))) continue;\n      const info = await stat(realCandidate);\n      if (!info.isFile()) continue;","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/server/src/extensions/cloud-uploads.ts#L96-L132","documentation":"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.","triggerScenarios":"Calling the cloud-upload endpoint when the server config yields an empty allowedRoots() list — no workspace roots were configured or all were filtered out.","commonSituations":"Server started without a workspace directory configured; workspace root path removed from config; misconfigured config file resulting in an empty roots array.","solutions":["Configure at least one authorized workspace root in the server config and restart","Verify the config file's workspace/root settings are being loaded (check for typos in keys or wrong config path)","Confirm allowedRoots() is not filtering out existing roots (e.g. non-existent directories being dropped)"],"exampleFix":"// before\n{ } // no workspace configured\n// after\n{ \"workspaceRoot\": \"/Users/me/projects\" }","handlingStrategy":"validation","validationCode":"if (allowedRoots(config).length === 0) return badRequest(\"no workspace roots\");","typeGuard":null,"tryCatchPattern":"try { await uploadCloud(p); } catch (e) { if (e.code === \"invalid_payload\") { openWorkspaceSettings(); } else throw e; }","preventionTips":["Startup check: fail fast when no roots configured","Gate upload features on roots presence","Test config loading in CI"],"tags":["config","security","uploads"],"backgroundTag":"missing-workspace-config","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}