paperclipai/paperclip · error · HttpError
error.message
Error message
error.message
What it means
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.
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.
Example fix
// before
await api.post(`/api/tool-connections/${id}/railway/ssh`, { action: "grant", grantId });
// after
try {
await api.post(`/api/tool-connections/${id}/railway/ssh`, { action: "grant", grantId });
} catch (e) {
if (e.status === 401 || e.status === 403) {
await updateToolConnectionCredentials(id, freshRailwayToken); // then retry
} else throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
const conn = await getConnection(id);
if (!conn.credentials?.railwayToken) throw new Error("Configure Railway credentials first"); Type guard
null
Try / catch
try { await api.post(`/tool-connections/${id}/railway/ssh`, body); }
catch (e) {
// e.status and e.message mirror the RailwayError
if (e.status === 401 || e.status === 403) await refreshRailwayToken(id);
else if (e.status === 404) await verifyRailwayResourceExists(conn);
else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- railway_workspace_discovery_failed
- Discord bot is not installed in the selected server
- Discord bot membership could not be verified
- Discord bot needs View Channels, Send Messages, Create…
- error.code
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/f29f687f74816c2c.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/routes/tool-access.ts:1690
);
if (!connection) return;
res.json(connection);
});
router.get("/tool-connections/:connectionId/services", async (req, res) => {
const connection = await getAccessibleResource(req, res, svc.getConnection(req.params.connectionId as string), "Tool connection not found");
if (!connection) return;
await assertToolConnectionConfigureAccess(req, connection);
res.json(await svc.listComposioServices(connection.id, getActorInfo(req)));
});
router.post("/tool-connections/:connectionId/railway/ssh", validate(configureRailwaySshSchema), async (req, res) => {
assertBoard(req);
const connection = await getAccessibleResource(req, res, svc.getConnection(req.params.connectionId as string), "Tool connection not found");
if (!connection) return;
await assertToolConnectionConfigureAccess(req, connection);
const setup = await svc.configureRailwaySsh(connection.id, connection.companyId, req.body, getActorInfo(req)).catch((error) => {
if (error instanceof RailwayError) throw new HttpError(error.status, error.message);
throw error;
});
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 } });
res.json(setup);
});
router.post("/tool-connections/:connectionId/services/:toolkitSlug/connect", async (req, res) => {
const connection = await getAccessibleResource(req, res, svc.getConnection(req.params.connectionId as string), "Tool connection not found");
if (!connection) return;
await assertToolConnectionConfigureAccess(req, connection);
const body = req.body && typeof req.body === "object" ? req.body as Record<string, unknown> : {};
const result = await svc.startComposioServiceConnect(connection.id, req.params.toolkitSlug as string, {
...(typeof body.authConfigId === "string" ? { authConfigId: body.authConfigId } : {}),
...(typeof body.callbackUrl === "string" ? { callbackUrl: body.callbackUrl } : {}),
});
await logActivity(db, {
companyId: connection.companyId,
actorType: "user",View on GitHub (pinned to 3f1d897a7c)