{"record":{"id":"f29f687f74816c2c","repo":"paperclipai/paperclip","slug":"error-message","errorCode":null,"errorMessage":"error.message","messagePattern":"error\\.message","errorType":"http","errorClass":"HttpError","httpStatus":null,"severity":"error","filePath":"server/src/routes/tool-access.ts","lineNumber":1690,"sourceCode":"    );\n    if (!connection) return;\n    res.json(connection);\n  });\n\n  router.get(\"/tool-connections/:connectionId/services\", async (req, res) => {\n    const connection = await getAccessibleResource(req, res, svc.getConnection(req.params.connectionId as string), \"Tool connection not found\");\n    if (!connection) return;\n    await assertToolConnectionConfigureAccess(req, connection);\n    res.json(await svc.listComposioServices(connection.id, getActorInfo(req)));\n  });\n\n  router.post(\"/tool-connections/:connectionId/railway/ssh\", validate(configureRailwaySshSchema), async (req, res) => {\n    assertBoard(req);\n    const connection = await getAccessibleResource(req, res, svc.getConnection(req.params.connectionId as string), \"Tool connection not found\");\n    if (!connection) return;\n    await assertToolConnectionConfigureAccess(req, connection);\n    const setup = await svc.configureRailwaySsh(connection.id, connection.companyId, req.body, getActorInfo(req)).catch((error) => {\n      if (error instanceof RailwayError) throw new HttpError(error.status, error.message);\n      throw error;\n    });\n    await logActivity(db, { companyId: connection.companyId, actorType: \"user\", actorId: req.actor.userId ?? \"board\", action: \"tool_connection.railway_ssh_updated\", entityType: \"tool_connection\", entityId: connection.id, details: { action: req.body.action, grantId: req.body.grantId, enabled: setup?.enabled ?? false } });\n    res.json(setup);\n  });\n\n  router.post(\"/tool-connections/:connectionId/services/:toolkitSlug/connect\", async (req, res) => {\n    const connection = await getAccessibleResource(req, res, svc.getConnection(req.params.connectionId as string), \"Tool connection not found\");\n    if (!connection) return;\n    await assertToolConnectionConfigureAccess(req, connection);\n    const body = req.body && typeof req.body === \"object\" ? req.body as Record<string, unknown> : {};\n    const result = await svc.startComposioServiceConnect(connection.id, req.params.toolkitSlug as string, {\n      ...(typeof body.authConfigId === \"string\" ? { authConfigId: body.authConfigId } : {}),\n      ...(typeof body.callbackUrl === \"string\" ? { callbackUrl: body.callbackUrl } : {}),\n    });\n    await logActivity(db, {\n      companyId: connection.companyId,\n      actorType: \"user\",","sourceCodeStart":1672,"sourceCodeEnd":1708,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/routes/tool-access.ts#L1672-L1708","documentation":"This is a pass-through failure: the Railway tool-connection SSH configuration endpoint calls svc.configureRailwaySsh, and when the service rejects with a RailwayError the route re-throws it as an HttpError carrying the Railway service's own status and message. The generic message 'error.message' represents whatever the Railway integration reported (e.g. invalid token, SSH key rejected, resource missing). Any non-RailwayError exception is re-thrown unchanged and becomes a 500.","triggerScenarios":"POST /tool-connections/:connectionId/railway/ssh with a body that configureRailwaySsh rejects — Railway API returns an error (bad credentials, missing service, invalid grant/action), or an unexpected exception inside the service.","commonSituations":"Expired or revoked Railway API token stored on the tool connection; configuring SSH against a deleted Railway project/environment; requesting an action (grant/revoke) the token lacks scope for; network failure to Railway surfacing as a raw error.","solutions":["Read the HTTP status and message returned by the route — it mirrors the underlying RailwayError — and fix the reported Railway-side cause.","Re-authenticate the tool connection: update the stored Railway token, then retry the SSH configuration call.","Verify the Railway project/service/environment IDs in the connection still exist and are accessible to the token.","If the error is not a RailwayError (500), inspect server logs for the raw exception in configureRailwaySsh."],"exampleFix":"// before\nawait api.post(`/api/tool-connections/${id}/railway/ssh`, { action: \"grant\", grantId });\n// after\ntry {\n  await api.post(`/api/tool-connections/${id}/railway/ssh`, { action: \"grant\", grantId });\n} catch (e) {\n  if (e.status === 401 || e.status === 403) {\n    await updateToolConnectionCredentials(id, freshRailwayToken); // then retry\n  } else throw e;\n}","handlingStrategy":"try-catch","validationCode":"const conn = await getConnection(id);\nif (!conn.credentials?.railwayToken) throw new Error(\"Configure Railway credentials first\");","typeGuard":"null","tryCatchPattern":"try { await api.post(`/tool-connections/${id}/railway/ssh`, body); }\ncatch (e) {\n  // e.status and e.message mirror the RailwayError\n  if (e.status === 401 || e.status === 403) await refreshRailwayToken(id);\n  else if (e.status === 404) await verifyRailwayResourceExists(conn);\n  else throw e;\n}","preventionTips":["Keep Railway tokens fresh and scoped to the target project","Validate project/service/environment IDs before configuring SSH","Catch non-RailwayError 500s by inspecting server logs","Retry only after fixing the Railway-side cause reported in the message"],"tags":["railway","integration","http-error"],"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"}