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. 1.Install the SDK (Node.js or Python).

    terminalbash
    npm 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 uvicorn
  2. 2.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.

    terminalbash
    railway variable set RUNHEED_DSN=runheed://pk_... RAILWAY_DEPLOYMENT_DRAINING_SECONDS=15
  3. 3.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.mjsjavascript
    import 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. 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.pypython
    import 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. 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).

    terminalbash
    railway 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

Plans from $29/moOther agents and editorsCancel any time from the billing portal