{"record":{"id":"d201fe20c6480a37","repo":"paperclipai/paperclip","slug":"railway-authorization-required","errorCode":"railway_authorization_required","errorMessage":"Reconnect Railway to authorize API access.","messagePattern":"Reconnect Railway to authorize API access\\.","errorType":"error_code","errorClass":"RailwayError","httpStatus":401,"severity":"error","filePath":"server/src/services/railway.ts","lineNumber":195,"sourceCode":"  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) {\n      await response.body?.cancel();\n      throw new RailwayError(\"railway_api_authorization_required\", \"Railway rejected API access. Reconnect with access to the required workspace or project. Hosted connection tokens are used only if Railway accepts them for API access.\", response.status);\n    }\n    if (!response.ok) {\n      await response.body?.cancel();","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/railway.ts#L177-L213","documentation":"Thrown by createRailwayClient (code railway_authorization_required, HTTP 401) when the supplied options.authorization does not match a Bearer token pattern (/^Bearer [^\\r\\n]+$/). Guards against calling the Railway API with an empty, malformed, or CR/LF-injected credential string.","triggerScenarios":"authorization is undefined/empty string; credential stored without the 'Bearer ' prefix; token containing newline characters; connection object not fully hydrated before client creation.","commonSituations":"Connection saved without completing OAuth token exchange; env var like RAILWAY_TOKEN missing so the prefix concat yields 'Bearer undefined'; copying a token with a trailing newline into config.","solutions":["Reconnect Railway to complete OAuth and store a valid access token","Ensure the token is stored/passed with the 'Bearer ' prefix and trimmed of whitespace","Check that the credential source (env var/DB row) is actually populated before creating the client","Never interpolate unsanitized user input into the header (CRLF guard is intentional)"],"exampleFix":"// before\nconst client = createRailwayClient({ authorization: process.env.RAILWAY_TOKEN }); // undefined\n// after\nconst token = process.env.RAILWAY_TOKEN?.trim();\nif (!token) throw new Error(\"RAILWAY_TOKEN not configured\");\nconst client = createRailwayClient({ authorization: `Bearer ${token}` });","handlingStrategy":"validation","validationCode":"function hasValidRailwayAuthorization(auth) {\n  return typeof auth === \"string\" && /^Bearer [^\\r\\n]+$/.test(auth);\n}\n// call before createRailwayClient\nif (!hasValidRailwayAuthorization(opts.authorization)) throw new Error(\"missing Railway token\");","typeGuard":"function isValidRailwayAuth(v) {\n  return typeof v === \"string\" && v.startsWith(\"Bearer \") && v.length > 7 && !/[\\r\\n]/.test(v);\n}","tryCatchPattern":"try {\n  const client = createRailwayClient(opts);\n} catch (e) {\n  if (e?.code === \"railway_authorization_required\") {\n    await initiateRailwayReconnect(conn); // token missing/malformed\n  } else throw e;\n}","preventionTips":["Trim tokens and always prefix with 'Bearer ' when storing","Fail fast on missing env vars instead of interpolating undefined","Complete the OAuth flow before persisting the connection","Never build auth headers from unsanitized user input (CRLF)"],"tags":["railway","authorization","missing-credentials","validation"],"backgroundTag":"authentication-required","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"}