{"record":{"id":"ad552ee77279d543","repo":"paperclipai/paperclip","slug":"error-code","errorCode":"error.code","errorMessage":"error.message","messagePattern":"error\\.message","errorType":"http","errorClass":"ToolGatewayHttpError","httpStatus":null,"severity":"error","filePath":"server/src/services/tool-gateway.ts","lineNumber":6121,"sourceCode":"      const sourceTemplateKey =\n        typeof connection.config.sourceTemplateKey === \"string\"\n          ? connection.config.sourceTemplateKey\n          : null;\n      const result = normalizeMcpToolResult(\n        payloadRecord.result,\n        \"mcp_http\",\n        false,\n        sourceTemplateKey,\n      );\n      await markRemoteConnectionHealth(\n        connection,\n        \"ok\",\n        \"Remote MCP server responded to tools/call.\",\n      );\n      return { result, headerSummary, execution };\n    } catch (error) {\n      if (error instanceof RailwayError) {\n        throw new ToolGatewayHttpError(error.status, error.message, error.code, { connectionId: connection.id, catalogEntryId: entry.id, execution });\n      }\n      if (error instanceof ToolGatewayHttpError) {\n        throw new ToolGatewayHttpError(\n          error.status,\n          error.message,\n          error.reasonCode,\n          {\n            ...error.details,\n            execution: error.details.execution ?? execution,\n          },\n        );\n      }\n      if (error instanceof Error && error.name === \"AbortError\") {\n        await markRemoteConnectionHealth(\n          connection,\n          \"error\",\n          \"Remote MCP tool call timed out.\",\n        );","sourceCodeStart":6103,"sourceCodeEnd":6139,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/tool-gateway.ts#L6103-L6139","documentation":"This is not an independent error but a rethrow path: when an MCP tools/call against a remote server fails, the gateway wraps RailwayError instances into a ToolGatewayHttpError preserving the original HTTP status, message, and reason code (error.code), plus connection/catalog/execution details. The 'message' and 'code' fields here are dynamic — they carry whatever the underlying Railway API/SSH client produced.","triggerScenarios":"Any railway_* tool execution that throws a RailwayError — e.g. Railway GraphQL API returns a non-2xx status, the API token is invalid, or the SSH command execution for railway_run-command fails — is caught here and rethrown with its original status/message/code.","commonSituations":"Expired or revoked Railway API token (401/403 from GraphQL); SSH credentials missing/wrong for run-command; Railway project/service ID referenced by the tool no longer exists (404); Railway API rate limiting or outage (5xx).","solutions":["Read the wrapped status/code in the error details to identify the actual Railway failure (auth vs not-found vs 5xx).","For auth failures, refresh/reconnect the Railway connection token and re-verify the API.","For run-command failures, check the connection's railwaySsh credential refs are populated and the SSH key is valid.","Retry after resolving; transient 5xx/rate-limit responses can be retried with backoff."],"exampleFix":"// before\ntry { await runRailwayTool(conn, tool, args); } catch { /* opaque failure */ }\n// after\ntry {\n  await runRailwayTool(conn, tool, args);\n} catch (e) {\n  if (e.status === 401 || e.status === 403) await reconnectRailway(conn.id); // refresh token\n  else throw e;\n}","handlingStrategy":"try-catch","validationCode":"const status = conn.config?.railwayApiStatus;\nif (status !== \"available\") await verifyRailwayApi(conn.id); // avoid most RailwayError causes up front","typeGuard":"const isRailwayFailure = (e) => e instanceof RailwayError || (typeof e.code === \"string\" && e.code.startsWith(\"railway_\"));","tryCatchPattern":"try {\n  return await gateway.invoke({ method: \"tools/call\", params: { name: railwayTool, arguments: args } });\n} catch (e) {\n  if (e.status === 401 || e.status === 403) await refreshRailwayToken(e.details.connectionId);\n  else if (e.status >= 500 || e.status === 429) await sleep(backoff()); // retry transient\n  else throw e;\n}","preventionTips":["Keep Railway API tokens valid and re-verify on schedule.","Ensure railwaySsh credentials are present before using run-command tools.","Validate project/service identifiers passed to railway tools still exist.","Implement backoff retry only for 429/5xx Railway statuses."],"tags":["railway","api-error","rethrow"],"backgroundTag":"upstream-api-error","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"}