PulseWatchPulsewatch

Reporting errors from your jobs

Send your job's stderr with a failure ping and PulseWatch parses the stack trace, groups it with identical failures, and puts the exception type, the failing line and a status (new, recurring, or a regression) directly in the alert. This is deterministic parsing: regex and hashing, no AI.

Sending stderr

# simplest: last 8 KB of combined output on failure
./job.sh 2>&1 | tail -c 8000 | curl -fsS --data-binary @- "https://pulsewatcher.up.railway.app/ping/<token>/fail"

Any non-JSON POST body works. A JSON body is treated as metrics instead, so send plain text for traces.

From application code

Catch the exception, post the stack trace to /fail, then re-raise so your own error handling still runs.

PING_URL="https://pulsewatcher.up.railway.app/ping/<token>"

./job.sh 2>&1 | tail -c 8000 | curl -fsS --data-binary @- "$PING_URL/fail"

Size caps per plan

We buffer at most the largest plan cap and read-and-discard the rest, so a huge upload is never held in memory. Anything past your cap is dropped, and the excerpt is flagged as truncated.

Free4 KB
Pro16 KB
Business64 KB

What we scrub, and what we store

Before anything is written down we strip ANSI colour codes and control characters, then redact anything that looks like a secret, replacing it with [REDACTED:kind].

Passwords & tokenspassword=, secret:, api_key, token, authorization and friends (including prefixed keys like db_password)
Authorization headersBearer … and Basic … (the scheme is kept, the credential is not)
JWTsthree-segment eyJ… values
Private keysthe whole -----BEGIN … PRIVATE KEY----- block
Connection stringspostgres://user:PASSWORD@host (the host is kept so you can still debug)
Cloud keysAWS AKIA… ids and long high-entropy values after = or :
Card numbersLuhn-valid 13–19 digit runs
Emails & IPson by default; Pro+ can turn this off when traces need them

Scrubbing is best-effort, not a guarantee, so do not deliberately print secrets. Traces stay internal to your account. They appear in your dashboard and alerts, and are never shown on public status pages. See the privacy policy for retention.

Supported languages

pythonTraceback blocks, chained exceptions (the last one is reported)
nodeat fn (file:line:col) frames, async stacks, Caused by / cause chains
javaat pkg.Class.m(File.java:N), Caused by chains (deepest cause wins), "... N more"
gopanic: / fatal error:, goroutine blocks, paired function + location lines
rubyfile.rb:N:in 'method': message (ExceptionClass) plus from frames
phpPHP Fatal error / Uncaught X, #N stack lines, "thrown in ... on line N"
dotnetSystem.X: message, ---> inner exceptions, "in File.cs:line N"
shellcommand not found, permission denied, and similar shell failures
unknownanything else; a message heuristic is kept, exception type is null

Output with no recognizable signature still produces a group: the language is unknown, the exception type is null, and the message falls back to the first error-ish line (or the last non-empty one).

How grouping works

A fingerprint is a hash of the language, exception type, the normalized message, and the file and function of the deepest frame that is not vendor or standard-library code. The line number is deliberately excluded, because it shifts whenever you edit anything above the failure. Including it would split one bug into many groups.

Normalizing replaces everything variable with a placeholder: numbers, hex, UUIDs, timestamps, quoted strings, paths, URLs, IPs, emails and durations. That means:

text
Timeout after 30s connecting to 10.0.0.5
Timeout after 45s connecting to 10.0.0.9
  -> both become: timeout after <dur> connecting to <ip>      SAME group

KeyError: 'user_id'
KeyError: 'order_id'
  -> both become: <str>                                        SAME group

KeyError: 'user_id'  vs  ValueError: 'user_id'
  -> exception type differs                                    DIFFERENT groups

Grouping KeyError on different keys together is intended: it is usually one bug. When a group really is too broad, or just noisy, mute it. That is the escape hatch.

Alerts, escalation and muting

  • New error. The first time a fingerprint appears it raises an alert, even if the monitor is already down. A new failure mode during an outage is news. Turn this off per monitor with alert_on_new_error.
  • Escalation. Set escalate_after_consecutive (minimum 2) and the same fingerprint failing N runs in a row sends one monitor.error_persisting alert per threshold crossing, not one per run.
  • Muting. A muted group raises no error alerts and collapses in the UI. Your monitor's own up/down alerting is unaffected. Optional auto-unmute after N days.
  • Dedup. At most one error-related alert per monitor per 60 seconds; several new fingerprints at once batch into a single message.

Webhook consumers get the full detail in details.error_group: see the webhooks page for the field table.