paperclipai/paperclip · error · RailwayError
railway_ssh_host_key_invalid
railway_ssh_host_key_invalid
Error message
The Railway host key is too long.
What it means
RailwayError (code railway_ssh_host_key_invalid, HTTP 400) thrown by validateRailwayKnownHosts when the provided known_hosts value exceeds 8192 characters. The validator caps host key input to a bounded size before storing it as a Railway connection secret. Oversized input is rejected up-front as a client error.
Solutions
- Trim input to only 1-5 verified ssh.railway.com lines (max 8192 chars total)
- Remove comments, aliases, @cert-authority/@revoked markers, and non-Railway host lines
- Regenerate with the correct line: 'ssh.railway.com ssh-ed25519 <base64>' (fetch via ssh-keyscan ssh.railway.com and filter)
- Validate length client-side: value.length <= 8192 before submitting
Example fix
// before
const knownHosts = fs.readFileSync("~/.ssh/known_hosts", "utf8"); // too large
// after
const knownHosts = fs.readFileSync("~/.ssh/known_hosts", "utf8")
.split("\n")
.filter((l) => l.startsWith("ssh.railway.com "))
.slice(0, 5)
.join("\n"); Defensive patterns
Strategy: validation
Validate before calling
function validateHostKeyLength(value) {
if (typeof value !== "string" || value.length > 8192) {
throw new Error("Known hosts input must be a string of at most 8192 characters");
}
return value;
} Type guard
null
Try / catch
try {
const normalized = await api.saveRailwayKnownHosts(value);
} catch (e) {
if (e.code === "railway_ssh_host_key_invalid" && value.length > 8192) {
showUserError("Paste only the ssh.railway.com lines (max 5), not your whole known_hosts file.");
return;
}
throw e;
} Prevention
- Paste only verified ssh.railway.com lines (1-5), never the entire known_hosts file
- Client-side check value.length <= 8192 before submit
- Strip comments, hashed entries, and non-Railway hosts before saving
- Document the 8192-char limit next to the connection form
When it happens
Trigger: Saving a Railway connection whose known_hosts field is longer than 8192 characters — e.g. pasting an entire ~/.ssh/known_hosts file, multiple full keys with comments, or base64 blobs with wrappers.
Common situations: User pastes their whole known_hosts file instead of only the ssh.railway.com lines; includes commented entries, hashed @-entries, or certificate lines; clipboard picks up extra content; automated import dumps all hosts.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- railway_target_mismatch
- railway_authorization_required
- railway_invalid_arguments
- railway_ssh_key_invalid
- railway_ssh_setup_required
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/b0e401232d1297d3.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/railway-ssh.ts:13
import { execFile, spawn } from "node:child_process";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { promisify } from "node:util";
import { randomBytes } from "node:crypto";
import { RailwayError, type RailwaySshInput } from "./railway.js";
import { redactSensitiveText } from "../redaction.js";
export const RAILWAY_SSH_SECRET_PATH = "railway.ssh_private_key";
export function validateRailwayKnownHosts(value: string): string {
if (value.length > 8192) throw new RailwayError("railway_ssh_host_key_invalid", "The Railway host key is too long.", 400);
const lines = value.trim().split(/\r?\n/);
if (lines.length === 0 || lines.length > 5 || lines.some((line) => !/^ssh\.railway\.com (ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp256) [A-Za-z0-9+/]+={0,2}$/.test(line))) {
throw new RailwayError("railway_ssh_host_key_invalid", "Paste verified known_hosts lines for ssh.railway.com only, without aliases, wildcards or comments.", 400);
}
return lines.join("\n") + "\n";
}
export async function generateRailwaySshKey(): Promise<{ publicKey: string; privateKey: string }> {
const directory = await mkdtemp(path.join(tmpdir(), "paperclip-railway-key-"));
try {
const keyPath = path.join(directory, "identity");
await promisify(execFile)("/usr/bin/ssh-keygen", ["-q", "-t", "ed25519", "-N", "", "-C", "paperclip-railway", "-f", keyPath], { timeout: 10_000, env: { PATH: "/usr/bin:/bin" } });
return { publicKey: (await readFile(`${keyPath}.pub`, "utf8")).trim(), privateKey: await readFile(keyPath, "utf8") };
} catch {
throw new RailwayError("railway_ssh_unavailable", "Generating a Railway key requires system OpenSSH (ssh-keygen) on the Paperclip runtime.", 422);
} finally { await rm(directory, { recursive: true, force: true }); }
}
View on GitHub (pinned to 3f1d897a7c)