Cloudflare Workers error tracking your coding agent can read

When a Worker throws, the visitor gets an error page and the stack trace is only in the live tail or Workers Logs. One small module wraps your fetch handler and sends each exception to Runheed with fetch. It reports inside ctx.waitUntil, so the response is not held up, and there is no package to install. Your coding agent then reads the error, the request and the stack trace over MCP.

Set it up

  1. 1.Store the pk_... part of your project DSN as a secret.

    terminalbash
    npx wrangler secret put RUNHEED_KEY
  2. 2.Add one module next to your Worker.

    src/runheed.tstypescript
    export interface RunheedEnv {
      RUNHEED_KEY?: string;
      RUNHEED_ENV?: string;
    }
    
    type FetchHandler<E> = (request: Request, env: E, ctx: ExecutionContext) => Response | Promise<Response>;
    
    const INGEST = 'https://runheed.com/api/ingest';
    
    function frames(stack = '') {
      return stack.split('\n').slice(1).flatMap((line) => {
        const m = line.match(/at (?:(.+?) \()?(\S+?):(\d+):(\d+)\)?$/);
        if (!m) return [];
        return [{ function: m[1] ?? '<anonymous>', filename: m[2], lineno: Number(m[3]), colno: Number(m[4]), inApp: !m[2].includes('node_modules') }];
      });
    }
    
    export async function reportError(err: unknown, env: RunheedEnv, request?: Request, extra: Record<string, unknown> = {}) {
      if (!env.RUNHEED_KEY) return;
      const e = err instanceof Error ? err : new Error(String(err));
      try {
        await fetch(INGEST, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json', 'X-Runheed-Key': env.RUNHEED_KEY },
          body: JSON.stringify({
            errorType: e.name || 'Error',
            errorMessage: e.message || String(err),
            stacktrace: frames(e.stack),
            platform: 'cloudflare-workers',
            runtime: 'workerd',
            environment: env.RUNHEED_ENV ?? 'production',
            request: request ? { method: request.method, url: request.url } : undefined,
            extra: { colo: (request as { cf?: { colo?: string } } | undefined)?.cf?.colo, ...extra },
            timestamp: new Date().toISOString(),
          }),
        });
      } catch {
        // Reporting failed; the Worker still answers.
      }
    }
    
    export function withRunheed<E extends RunheedEnv>(handler: FetchHandler<E>): FetchHandler<E> {
      return async (request, env, ctx) => {
        try {
          return await handler(request, env, ctx);
        } catch (err) {
          ctx.waitUntil(reportError(err, env, request));
          return new Response('Internal error', { status: 500 });
        }
      };
    }
  3. 3.Wrap the fetch handler. Errors you catch yourself can be sent with ctx.waitUntil(reportError(err, env, request, { anything: "useful" })).

    src/index.tstypescript
    import { withRunheed, type RunheedEnv } from './runheed';
    
    interface Env extends RunheedEnv {
      // your bindings (KV, D1, R2...)
    }
    
    export default {
      fetch: withRunheed<Env>(async (request, env, ctx) => {
        const url = new URL(request.url);
        // ...your code
        return Response.json({ ok: true, path: url.pathname });
      }),
    } satisfies ExportedHandler<Env>;
  4. 4.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
    npx wrangler deploy
    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

  • Any exception thrown inside the wrapped fetch handler, with the method, URL, Cloudflare data center and stack trace
  • Errors you report yourself with reportError, with whatever context you pass

What it does not do

  • scheduled, queue and email handlers need their own try/catch with reportError
  • Exceeded CPU or memory limits end the Worker before any code can report them
  • Stack traces point at lines in the bundled Worker script that Wrangler uploads, not your source files
  • 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

Common Cloudflare Workers errors

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