{"record":{"id":"1dab9f27fcdf838c","repo":"paperclipai/paperclip","slug":"railway-workspace-required","errorCode":"railway_workspace_required","errorMessage":"No authorized Railway workspace was found. Reconnect Railway and select a workspace to enable direct operations.","messagePattern":"No authorized Railway workspace was found\\. Reconnect Railway and select a workspace to enable direct operations\\.","errorType":"error_code","errorClass":"RailwayError","httpStatus":403,"severity":"error","filePath":"server/src/services/railway.ts","lineNumber":190,"sourceCode":"  let response = await list(headers);\n  if (response.status === 400) {\n    await response.body?.cancel();\n    response = await list(await initializeMcpHttpSession({ send, headers, requestId: \"paperclip-railway-workspace-probe\" }));\n  }\n  if (!response.ok) {\n    await response.body?.cancel();\n    throw new RailwayError(\"railway_workspace_discovery_failed\", \"Railway's hosted connection is connected, but workspace access could not be checked. Refresh actions to try again.\");\n  }\n  const body = await boundedResponseText(response, options.signal);\n  let data: Record<string, any>;\n  try {\n    const payload = record(parseMcpHttpResponseBody(body, response.headers.get(\"content-type\")));\n    const result = record(payload.result);\n    if (payload.error || result.isError) throw new Error(\"Workspace discovery failed\");\n    data = record(result.structuredContent ?? JSON.parse(result.content?.find((item: any) => item.type === \"text\")?.text ?? \"{}\"));\n  } catch { throw new RailwayError(\"railway_workspace_discovery_failed\", \"Railway could not list authorized workspaces. Refresh actions or reconnect and select a workspace.\"); }\n  const workspaceId = Array.isArray(data.workspaces) ? data.workspaces.find((workspace) => id.safeParse(workspace?.id).success)?.id : undefined;\n  if (!workspaceId) throw new RailwayError(\"railway_workspace_required\", \"No authorized Railway workspace was found. Reconnect Railway and select a workspace to enable direct operations.\", 403);\n  return workspaceId;\n}\n\nexport function createRailwayClient(options: RailwayClientOptions) {\n  if (!/^Bearer [^\\r\\n]+$/.test(options.authorization)) throw new RailwayError(\"railway_authorization_required\", \"Reconnect Railway to authorize API access.\", 401);\n  const secret = options.authorization.slice(7);\n  const redact = (value: unknown) => JSON.parse(redactSensitiveText(JSON.stringify(value).split(secret).join(\"[REDACTED]\")));\n\n  async function query(document: string, variables: Record<string, unknown>): Promise<Record<string, any>> {\n    options.signal.throwIfAborted();\n    let response: Response;\n    try {\n      response = await options.request(RAILWAY_API_URL, { method: \"POST\", redirect: \"error\", signal: options.signal, headers: { \"content-type\": \"application/json\", Authorization: options.authorization }, body: JSON.stringify({ query: document, variables }) });\n    } catch (error) {\n      if (options.signal.aborted) throw options.signal.reason;\n      throw new RailwayError(\"railway_request_failed\", \"Railway could not be reached. A deployment request may have succeeded; inspect deployment status before retrying.\");\n    }\n    if (response.status === 401 || response.status === 403) {","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/railway.ts#L172-L208","documentation":"Thrown (code railway_workspace_required, HTTP 403) when workspace discovery succeeds but no workspace in the response matches the expected workspace ID schema, or the workspaces array is absent. The connection is authenticated, but no usable authorized workspace exists to scope direct Railway operations.","triggerScenarios":"Account has zero workspaces; the only workspaces have missing/malformed IDs failing zod id.safeParse; connection was created for a different account than expected; workspaces field renamed in the MCP response.","commonSituations":"New Railway account with no workspace yet; revoked workspace membership; Railway MCP response-shape change; user connected with a personal token scoped to an empty org.","solutions":["Reconnect Railway and explicitly select a workspace during consent, as the message instructs","Ensure the connected Railway account actually belongs to at least one workspace","Verify workspace membership on the Railway dashboard (accept any pending invites)","If shapes changed, update the workspace ID validation/adapter"],"exampleFix":"// before\nconst ws = data.workspaces?.find(w => id.safeParse(w?.id).success)?.id;\nif (!ws) throw new RailwayError(\"railway_workspace_required\", ...);\n// after (user side: create/select a workspace first)\nawait ensureRailwayWorkspace(account); // create workspace via dashboard if none\nconst ws = data.workspaces?.find(w => id.safeParse(w?.id).success)?.id;","handlingStrategy":"validation","validationCode":"async function railwayWorkspaceConfigured(conn) {\n  const ws = await probeRailwayWorkspaces(conn); // raw discovery\n  return Array.isArray(ws) && ws.some(w => typeof w?.id === \"string\" && w.id.length > 0);\n}","typeGuard":"function hasAuthorizedWorkspace(data) {\n  return Array.isArray(data?.workspaces) &&\n    data.workspaces.some(w => w != null && typeof w.id === \"string\");\n}","tryCatchPattern":"try {\n  const ws = await client.workspaceId();\n} catch (e) {\n  if (e?.code === \"railway_workspace_required\") {\n    throw new ConfigurationError(\"Railway connection has no workspace; reconnect and select one.\");\n  } else throw e;\n}","preventionTips":["Always select a workspace during Railway connection consent","Create at least one workspace on a new Railway account before connecting","Periodically re-check workspace membership when team roles change","Validate the stored workspace ID against discovery before operations"],"tags":["railway","workspace","authorization","configuration"],"backgroundTag":"empty-result-set","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}