Railway error tracking your coding agent can read
Updated 2026-11-07
A service on Railway is a long-running container, and when it throws, the stack trace lands in the deploy logs where nobody is watching. The Runheed SDK reports each exception with the commit Railway deployed (RAILWAY_GIT_COMMIT_SHA) and the Railway environment it ran in. One setting matters: on a deploy, Railway sends SIGTERM to the old deployment and by default gives it 0 seconds before SIGKILL, so the setup below gives it a few seconds of draining time. The Node.js SDK batches events for up to 5 seconds, so the Node.js server flushes on SIGTERM; the Python SDK sends each event before capture_exception returns. Your coding agent then reads the error and the stack trace over MCP.
Set it up
1.Install the SDK (Node.js or Python).
terminalbashnpm install https://runheed.com/dl/runheed-0.1.5.tgz express # or, for Python: pip install https://runheed.com/dl/runheed-0.1.5-py3-none-any.whl fastapi uvicorn2.Add your DSN and a draining time as service variables (or in the service's Variables tab). RAILWAY_DEPLOYMENT_DRAINING_SECONDS is the time between SIGTERM and SIGKILL; it is 0 unless you set it.
terminalbashrailway variable set RUNHEED_DSN=runheed://pk_... RAILWAY_DEPLOYMENT_DRAINING_SECONDS=153.Node.js with Express: initialize first, add the error handler after your routes, and flush on SIGTERM. Set the start command to node server.mjs: Railway's docs note that when npm, Yarn or pnpm starts the app, the package manager gets the SIGTERM instead of your app.
server.mjsjavascriptimport express from 'express'; import runheed, { expressErrorHandler } from 'runheed'; runheed.init({ dsn: process.env.RUNHEED_DSN, environment: process.env.RAILWAY_ENVIRONMENT_NAME ?? 'development', release: process.env.RAILWAY_GIT_COMMIT_SHA, }); const app = express(); app.use(express.json()); app.post('/api/checkout', async (req, res) => { const { cartId } = req.body; // ...your code res.json({ ok: true, cartId }); }); app.use(expressErrorHandler()); const server = app.listen(process.env.PORT ?? 3000); // On a deploy Railway sends SIGTERM, then SIGKILL after RAILWAY_DEPLOYMENT_DRAINING_SECONDS. // The SDK batches events for up to 5 seconds: finish open requests, send the batch, then exit. process.on('SIGTERM', () => { server.close(async () => { await runheed.flush(); process.exit(0); }); });4.Python with FastAPI: the SDK sends each event before capture_exception returns, so there is nothing to flush. Set the start command to uvicorn main:app --host 0.0.0.0 --port $PORT.
main.pypythonimport os import runheed from fastapi import FastAPI from runheed import FastAPIIntegration runheed.init( dsn=os.environ["RUNHEED_DSN"], environment=os.environ.get("RAILWAY_ENVIRONMENT_NAME", "development"), release=os.environ.get("RAILWAY_GIT_COMMIT_SHA"), ) app = FastAPI() integration = FastAPIIntegration() integration.setup(runheed.get_client()) app.add_middleware(integration.create_middleware()) @app.post("/api/checkout") async def checkout(body: dict): # ...your code return {"ok": True, "cart_id": body["cartId"]}5.Deploy, then connect your agent with the server URL and a sign-in (Claude Code shown; Cursor, claude.ai and ChatGPT take the same URL).
terminalbashrailway up claude mcp add --transport http runheed https://runheed.com/api/mcp
The npm and PyPI releases are not out yet, so these commands install the same package files straight from runheed.com. Nothing else changes when the registry versions ship.
What it catches
- Errors thrown in Express routes and middleware, or raised in FastAPI routes, with the method, URL, the commit Railway deployed and the environment name
- Uncaught exceptions and unhandled promise rejections in the Node.js process, and uncaught exceptions in Python
- Errors you report yourself with captureException / capture_exception and any context you pass
What it does not do
- With the default draining time of 0 seconds, SIGKILL follows SIGTERM at once: requests still running are cut off and Node.js events still in the batch are lost
- Running out of memory ends the process before it can send what is left in the batch
- Django and Flask on Railway use the hooks on their own setup pages
- Browser errors are not captured; there is no browser SDK yet
Then ask your agent
> what broke in production since the last deploy? fix the top one and resolve it with the commit hash
Connect Runheed first: Claude Code, Cursor or another client. Then see how the agent fixes and resolves an error.
Common Railway (Node.js and Python) errors
- SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
- TypeError: Cannot read properties of undefined (reading 'x')
- [UnhandledPromiseRejection: This error originated either by throwing inside of an async...
- TypeError: fetch failed
- KeyError: 'email'
- ModuleNotFoundError: No module named 'x'
- psycopg.errors.UniqueViolation: duplicate key value violates unique constraint "users_e...