{"record":{"id":"f03d5cfc5d9db9ff","repo":"paperclipai/paperclip","slug":"workspace-discovery-failed","errorCode":null,"errorMessage":"Workspace discovery failed","messagePattern":"Workspace discovery failed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/src/services/railway.ts","lineNumber":186,"sourceCode":"  const list = (requestHeaders: Record<string, string>) => send({\n    method: \"POST\", headers: mcpHttpRequestHeaders(requestHeaders),\n    body: JSON.stringify({ jsonrpc: \"2.0\", id: \"paperclip-railway-workspace-probe\", method: \"tools/call\", params: { name: \"list-workspaces\", arguments: {} } }),\n  });\n  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) {","sourceCodeStart":168,"sourceCodeEnd":204,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/railway.ts#L168-L204","documentation":"Internal sentinel thrown inside the try block of discoverRailwayWorkspace when the parsed MCP response carries an error payload or result.isError is true. It is immediately caught and rethrown as RailwayError 'railway_workspace_discovery_failed', so callers should never observe it directly.","triggerScenarios":"MCP list-workspaces tool responds with payload.error set, or result.isError === true, e.g. Railway MCP reports a tool-level failure (invalid session, tool not permitted, upstream Railway API failure).","commonSituations":"MCP session became invalid between init and call; Railway MCP tool returns an isError result for an account with API problems; malformed structuredContent causing record() to throw.","solutions":["Retry via 'Refresh actions' to reinitialize the MCP session","Reconnect the Railway connection to get a new session and token","Inspect Railway MCP response/logs for the underlying tool error","Retry after confirming Railway status is healthy"],"exampleFix":"// before\nlet client = createRailwayClient({ authorization }); // MCP session stale\nawait client.workspaceId();\n// after\nawait refreshRailwayConnectionSession(connection); // force new initializeMcpHttpSession\nlet client = createRailwayClient({ authorization });","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isMcpErrorResult(payload) {\n  return Boolean(payload?.error || payload?.result?.isError);\n}","tryCatchPattern":"try {\n  const ws = await client.workspaceId();\n} catch (e) {\n  if (e?.code === \"railway_workspace_discovery_failed\") {\n    await reconnectRailwaySession(conn); // stale MCP session is the usual cause\n  } else throw e;\n}","preventionTips":["Reinitialize MCP sessions proactively instead of reusing long-lived ones","Log the underlying MCP error/isError result for diagnosis","Treat any isError MCP result as a session-refresh signal"],"tags":["railway","mcp","internal","workspace-discovery"],"backgroundTag":"api-error-response","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T06:17:15.046Z"}