paperclipai/paperclip · error · RailwayError
railway_workspace_discovery_failed
railway_workspace_discovery_failed
Error message
Railway's hosted connection is connected, but workspace access could not be checked. Refresh actions to try again.
What it means
Thrown by discoverRailwayWorkspace when the MCP-over-HTTP probe used to list the connected Railway account's workspaces returns a non-OK HTTP response (after a 400-triggered session re-initialization also fails). It signals that the hosted connection is authenticated enough to be 'connected', but the workspace discovery call itself failed, so the code cannot determine which workspace to operate on.
Solutions
- Click 'Refresh actions' (re-run the connection action probe) as the message suggests
- Disconnect and reconnect the Railway hosted connection to obtain a fresh token/session
- Verify network/proxy access to Railway's MCP endpoint from the server host
- Check Railway status for an ongoing incident and retry later
Example fix
// before
const client = createRailwayClient({ authorization: staleAuthorization });
await client.workspaceId(); // throws railway_workspace_discovery_failed
// after
if (!(await railwayActionsHealthy(connection))) {
await reconnectRailway(connection); // refresh token/session before use
}
const client = createRailwayClient({ authorization: freshAuthorization }); Defensive patterns
Strategy: retry
Validate before calling
function canProbeRailway(conn) {
return typeof conn.authorization === "string" && conn.authorization.startsWith("Bearer ") && !conn.stale;
} Try / catch
try {
const ws = await client.workspaceId();
} catch (e) {
if (e?.code === "railway_workspace_discovery_failed") {
await backoffRetry(() => refreshActionsAndRetry(conn), 3); // transient-safe retry
} else throw e;
} Prevention
- Refresh the connection's actions/token before long-running operations
- Monitor Railway status for MCP/API incidents
- Avoid proxies that strip or rewrite the MCP HTTP endpoint
- Surface a 'Refresh actions' affordance to users rather than failing silently
When it happens
Trigger: The MCP HTTP endpoint returns 400 on the initial tools/call (session re-init attempted) and the retried list-workspaces request still returns a non-2xx status; network/proxy errors; Railway MCP service outage; expired hosted-connection token rejected at HTTP level.
Common situations: Railway-hosted OAuth token revoked or expired server-side; corporate proxy blocking the MCP endpoint; transient Railway incident; stale connection after Railway changed session handling.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- error.message
- Migrator producer lookup failed: HTTP
- railway_request_failed
- Workspace discovery failed
- Anthropic Managed Agents request failed with HTTP
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/2c591bfb64cf800b.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/railway.ts:179
return Buffer.concat(chunks).toString("utf8");
}
/** Discover a consented workspace without requesting account-wide API access. */
export async function discoverRailwayWorkspace(options: RailwayClientOptions): Promise<string> {
const send = (init: RequestInit) => options.request(RAILWAY_MCP_URL, { ...init, redirect: "error", signal: options.signal });
const headers = { Authorization: options.authorization };
const list = (requestHeaders: Record<string, string>) => send({
method: "POST", headers: mcpHttpRequestHeaders(requestHeaders),
body: JSON.stringify({ jsonrpc: "2.0", id: "paperclip-railway-workspace-probe", method: "tools/call", params: { name: "list-workspaces", arguments: {} } }),
});
let response = await list(headers);
if (response.status === 400) {
await response.body?.cancel();
response = await list(await initializeMcpHttpSession({ send, headers, requestId: "paperclip-railway-workspace-probe" }));
}
if (!response.ok) {
await response.body?.cancel();
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.");
}
const body = await boundedResponseText(response, options.signal);
let data: Record<string, any>;
try {
const payload = record(parseMcpHttpResponseBody(body, response.headers.get("content-type")));
const result = record(payload.result);
if (payload.error || result.isError) throw new Error("Workspace discovery failed");
data = record(result.structuredContent ?? JSON.parse(result.content?.find((item: any) => item.type === "text")?.text ?? "{}"));
} catch { throw new RailwayError("railway_workspace_discovery_failed", "Railway could not list authorized workspaces. Refresh actions or reconnect and select a workspace."); }
const workspaceId = Array.isArray(data.workspaces) ? data.workspaces.find((workspace) => id.safeParse(workspace?.id).success)?.id : undefined;
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);
return workspaceId;
}
export function createRailwayClient(options: RailwayClientOptions) {
if (!/^Bearer [^\r\n]+$/.test(options.authorization)) throw new RailwayError("railway_authorization_required", "Reconnect Railway to authorize API access.", 401);
const secret = options.authorization.slice(7);
const redact = (value: unknown) => JSON.parse(redactSensitiveText(JSON.stringify(value).split(secret).join("[REDACTED]")));View on GitHub (pinned to 3f1d897a7c)