Alerting¶
This page explains BenchDB's scheduled alerts. It covers alert rules, how
benchdb admin alerts evaluate opens and resolves them, and how
benchdb admin alerts deliver sends the resulting events. It is for operators
who schedule these jobs and for users who manage rules.
Alerts run the same per-run comparison as benchdb ci report, on a schedule
instead of inside a CI job. BenchDB stores the rules, the state of each rule,
and an event each time a rule opens or resolves. For synchronous pull request
checks, use CI reporting instead.
Alert rules¶
Each rule belongs to one user. You set these fields when you create or update a rule:
| Field | Required | Default | Rules |
|---|---|---|---|
name |
yes | Must not be blank. | |
repository |
yes | Repository URL. BenchDB normalizes it. | |
baseline |
no | fork_point |
fork_point, parent, or latest_default. |
threshold |
no | 5 |
Pairwise percent-change threshold. Must be greater than zero. |
threshold_z |
no | 2 |
Lookback z-score threshold. Must be greater than zero. |
run_reason |
no | none | Only consider results with this run reason. A blank value means no filter. |
enabled |
no | true |
The evaluator skips disabled rules. |
BenchDB also returns id, user_id, state (inactive or open),
created_at, updated_at, and last_evaluated_at. An invalid field returns
422.
If an update changes repository, baseline, threshold, threshold_z, or
run_reason, BenchDB sets the rule back to inactive and clears
last_evaluated_at. Changing only name or enabled keeps the state.
Signed-in users can create rules, see their state, delete rules, and view
recent events on the /account page.
Alert rule API¶
| Method and path | Purpose |
|---|---|
POST /api/alert-rules |
Create a rule. Returns 201. |
GET /api/alert-rules |
List your rules. |
GET /api/alert-rules/{id} |
Get one rule. |
PUT /api/alert-rules/{id} |
Replace a rule's fields. |
DELETE /api/alert-rules/{id} |
Delete a rule. Returns 204. |
GET /api/alert-rules/{id}/events |
List a rule's events. limit defaults to 50 and must be 1 to 500. |
These endpoints need an API token or session cookie that belongs to a user.
Without a credential they return 401. The static operator token gets 403,
because rules belong to users. An unknown rule id and another user's rule both
return 404. See Authentication and tokens for the full
set of checks.
Evaluation¶
Schedule evaluation on the server host, using the server's archive directory:
export BENCHDB_DB_URL="postgres://..."
export BENCHDB_INTENDED_BASE_URL="https://benchdb.example"
export BENCHDB_DATA_DIR="/var/lib/benchdb"
benchdb admin alerts evaluate --format json
Evaluation needs the server's archive volume and matching DuckDB extensions, as well as its database. A job with only database access cannot read archived history. On Kubernetes, schedule execution inside the server pod:
On a host, cron can use the server's directory and environment. Initialization has a 30-second deadline and can proceed during archive export. Each evaluator process has its own DuckDB budget; account for it alongside the server.
For each enabled rule, the evaluator:
- Finds the newest result in the rule's repository, limited to the rule's
run_reasonwhen set. If there is none, it counts the rule asno_candidate. - Builds a CI report for that result's run and commit, with the rule's baseline and thresholds.
- Leaves out contender rows marked as the start of a distribution change. If
that leaves no rows, the report status becomes
skipped. - Updates the rule from the report status:
| Report status | Rule is inactive |
Rule is open |
|---|---|---|
failure |
Record an opened event and set the rule to open. |
No change. |
success |
No change. | Record a resolved event and set the rule to inactive. |
skipped or action_required |
No change. | No change. |
A report is failure when at least one row is a lookback regression. That
requires the contender to be outside the history band, at least 3 standard
deviations from its own baseline result, more than 2% away from it, and
larger than the practical tolerance (30 ms by default for timings).
Regression analysis explains the rule.
If a rule is disabled, or its repository, baseline, thresholds, or run reason change while the evaluator runs, the evaluator leaves that rule alone on that pass.
Each event stores the report status, status reason, repository, run id, commit SHA, report URL, and report summary JSON.
The command prints a summary to stdout with the counts rules,
evaluated, opened, resolved, unchanged, skipped, no_candidate, and
failed. It prints a warning to stderr for each rule that failed, and exits
1 if any rule failed.
The evaluator uses commit metadata already in the database, so it does not
need GITHUB_API_TOKEN. BENCHDB_INTENDED_BASE_URL is optional. Set it in
deployed environments so report URLs are absolute. GitHub Checks and Slack
messages include a report link only when the URL is absolute.
Test cadence or rule-count changes against a production clone first.
Delivery¶
benchdb admin alerts deliver sends stored alert events to one channel and
target per run. Run it after evaluation:
export BENCHDB_DB_URL="postgres://..."
export BENCHDB_ALERT_WEBHOOK_URL="https://hooks.example/benchdb"
benchdb admin alerts deliver --format json
Each run does the following for its channel and target:
- Creates a pending delivery row for each event that has none yet, up to
--limit. - Claims due rows one at a time with
FOR UPDATE SKIP LOCKED, and setsnext_attempt_atto now plus--retry-afterbefore sending. - Marks a successful send as
delivered. - Records a failed send with
last_error,last_attempt_at, andnext_attempt_at.
Delivery rows are unique per event, channel, and target. Rerunning the command
never resends a delivered event. Overlapping runs never claim the same row. If
a run crashes mid-send, the row becomes due again after --retry-after. The
CLI requires --retry-after to be greater than --timeout so a send cannot
outlast its claim.
The summary on stdout has enqueued, attempted, delivered, and failed
counts. It never includes the database URL or target URL. Each failed send
prints a warning to stderr, and the command exits 1 if any send failed.
See the CLI reference for all flags, defaults, and environment variables.
Webhook¶
The webhook channel is the default. It sends a POST with
Content-Type: application/json and User-Agent: benchdb-alert-delivery. Any
status outside 2xx counts as a failed send. The body has the delivery id,
channel, and alert event:
{
"delivery_id": "018f...",
"channel": "webhook",
"event": {
"id": "018f...",
"rule_id": "018f...",
"kind": "opened",
"status": "failure",
"status_reason": "lookback regression detected",
"run_id": "run-123",
"commit_sha": "abcdef",
"repository": "https://github.com/org/repo",
"report_url": "https://benchdb.example/ci/report?baseline=fork_point&commit_sha=abcdef&repository=https%3A%2F%2Fgithub.com%2Forg%2Frepo&run_ids=run-123",
"summary": {"regressions": 1},
"created_at": "2026-06-19T00:00:00Z"
}
}
Slack¶
export BENCHDB_DB_URL="postgres://..."
export BENCHDB_ALERT_SLACK_WEBHOOK_URL="https://hooks.slack.com/services/..."
benchdb admin alerts deliver --channel slack --format json
Slack delivery posts to an incoming webhook. The message has fallback text
and Block Kit sections with the status, the reason, and a link to the report.
GitHub Check Runs¶
export BENCHDB_DB_URL="postgres://..."
export BENCHDB_ALERT_GITHUB_REPOSITORY="https://github.com/org/repo"
export GITHUB_TOKEN="..."
benchdb admin alerts deliver --channel github-check --format json
The token needs checks:write on the repository. The CLI reads
--github-token, then BENCHDB_ALERT_GITHUB_TOKEN, GITHUB_TOKEN, and
GITHUB_API_TOKEN.
BenchDB creates a completed Check Run named BenchDB alert on the event's
commit. A failure report becomes a failing check, success a passing check,
and any other status a neutral check.
The target is a repository URL, not a webhook. BenchDB only queues events whose rule repository matches the target, so one repository's alerts never appear on another repository.
GitHub commit comments¶
export BENCHDB_DB_URL="postgres://..."
export BENCHDB_ALERT_GITHUB_REPOSITORY="https://github.com/org/repo"
export GITHUB_TOKEN="..."
benchdb admin alerts deliver --channel github-comment --format json
This channel posts a comment on the event's commit. It uses the same
repository target, token lookup, and repository matching as Check Runs. The
token needs contents:write on the repository.
Email¶
export BENCHDB_DB_URL="postgres://..."
export BENCHDB_ALERT_EMAIL_SMTP_ADDR="smtp.example:587"
export BENCHDB_ALERT_EMAIL_FROM="BenchDB Alerts <alerts@example.com>"
export BENCHDB_ALERT_EMAIL_TO="ops@example.com,perf@example.com"
export BENCHDB_ALERT_EMAIL_USERNAME="smtp-user"
export BENCHDB_ALERT_EMAIL_PASSWORD="..."
benchdb admin alerts deliver --channel email --format json
BENCHDB_ALERT_EMAIL_USERNAME and BENCHDB_ALERT_EMAIL_PASSWORD are
optional, but you must set both or neither. BenchDB uses STARTTLS when the
server offers it. If you set credentials, the server must support SMTP AUTH.
The delivery target is the normalized recipient list, with duplicates removed. The SMTP server and password are not part of the target, so retries stay unique per event and recipient set. Each message is plain text with the status, kind, reason, report URL, repository, commit, run, rule, and event id.
Adding a channel¶
benchalerts, the Python alert package, is retired and BenchDB does not
support it. A new notification system belongs in BenchDB as another delivery
channel. It should read the stored alert events, use the same delivery outbox,
and record its own delivery state.