{{ message }}
feat(alerting): Opsgenie adapter (plan 26) - #2248
Merged
Merged
Conversation
duyet
enabled auto-merge (squash)
July 3, 2026 16:59
Member
Author
This was referenced Jul 4, 2026
…an 26)
Mirrors the PagerDuty adapter: severity→priority mapping (P1/P2/P3), a
stable chmonitor:{hostId}:{metric} alias so repeat firings collapse to one
Opsgenie alert, host/metric/chmonitor tags, string-only details, runbook
URLs in the description, and detect() for api(.eu).opsgenie.com. Registered
in ADAPTERS before the generic-json fallback.
Co-Authored-By: duyetbot <bot@duyet.net>
HEALTH_ALERT_OPSGENIE_API_KEY (default '') and HEALTH_ALERT_OPSGENIE_REGION
('us'|'eu', default 'us'), mirroring the getServerThresholdOverrides
companion-function style. Returns null when unset so Opsgenie delivery is
opt-in and fails open. Kept separate from getServerAlertConfig's
AlertSettings shape, which is shared with client localStorage and asserted
deeply by its own tests — an API key is a server-only secret.
Co-Authored-By: duyetbot <bot@duyet.net>
dispatchOpsgenie(payload, {apiKey, region}) applies the GenieKey auth header
and region-appropriate Alert API base URL, POSTing a create request on
trigger and a close-alias request on recovery. Every outbound URL runs
through validateHostUrl first (same SSRF guard as the health webhook proxy
and browser-connections), and the function never throws — a delivery
failure returns false so callers (the health sweep) stay fail-open.
Co-Authored-By: duyetbot <bot@duyet.net>
Fans the sweep's per-finding dedup decision out to both the webhook and Opsgenie channels independently -- each channel's delivery failure is isolated (never throws into the sweep) and "notified" is only persisted once at least one channel actually delivered, so a fully-failed sweep still retries next run. Co-Authored-By: duyetbot <bot@duyet.net>
…lan 26) The Opsgenie API key is a server-only env secret (HEALTH_ALERT_OPSGENIE_API_KEY) that must never round-trip to the browser, unlike the webhook URL the user types in -- so instead of a key input field, the settings dialog shows a read-only configured/region badge (GET /api/v1/health/opsgenie-test) and a "Send test" button that fires a real dispatch through the server's own config (POST /api/v1/health/opsgenie-test, gated the same way as the webhook proxy). Co-Authored-By: duyetbot <bot@duyet.net>
duyet
force-pushed
the
advisor/26-opsgenie-adapter
branch
from
July 4, 2026 05:18
076eeef to
50dc3b4
Compare
Contributor
☁️ Cloudflare Preview Deployment
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Adds an Opsgenie notification adapter with parity to the existing Slack/Discord/Telegram/PagerDuty pattern (plan 26):
apps/dashboard/src/lib/health/adapters/opsgenie.ts):buildOpsgenieBody(payload)builds an Opsgenie Alert API v2 create-alert body — severity → priority (critical→P1,warning→P2), a stablealias(chmonitor:{hostId}:{metric}) so repeat firings collapse to one alert, host+metric tags, string-onlydetails, runbook links indescription.opsgenieAdapter.detectmatchesapi.opsgenie.com/api.eu.opsgenie.com. No network/auth in this layer.opsgenieAdapterregistered inadapters/index.ts(ADAPTERS, before the generic fallback); types/functions re-exported.getServerOpsgenieConfig()inserver-alert-config.tsreadsHEALTH_ALERT_OPSGENIE_API_KEY(empty ⇒null, fail-open) andHEALTH_ALERT_OPSGENIE_REGION(us/eu, defaultus).AlertSettingsshape unchanged.opsgenie-dispatch.ts): POSTs create on trigger / closes the alias on recovery, with theGenieKeyauth header and region-correct base URL, through the same SSRF guard (validateHostUrl) other outbound fetches use. Never throws — fails open into the sweep.server-sweep.ts): the per-finding dedup decision now fans out to webhook and Opsgenie independently; each channel's failure is isolated, and "notified" is only persisted once at least one channel delivered (so a fully-failed sweep still retries).health-settings-dialog.tsxgets an Opsgenie section with a configured/region status badge and a "Send test" button, backed by a newGET/POST /api/v1/health/opsgenie-testroute (POST write-gated like the webhook proxy).Note on the settings-UI interpretation
The plan's done-criteria says "Opsgenie key field." The Opsgenie API key
is documented (
server-alert-config.ts) as a server-only secret that mustnever round-trip to the browser — a localStorage-backed key input (like the
webhook URL field) would either be non-functional or a secret-leak path given
that design. Implemented instead as a read-only configured/region status +
a server-side test-send button that exercises the server's own env-configured
key. Flagging this explicitly rather than silently deviating from the
checklist wording.
Note on
server-sweep.tsThis branch also carries the (uncommitted, pre-existing) Opsgenie dispatch
wiring into
runHealthSweep— split into its own commit since other agentsare working on
server-sweep.ts/server-alert-config.tsin parallel forplan 30's routing model. Expect a rebase.
Known coverage gap
runHealthSweep's newif (opsgenieConfig)fan-out branch isn't covered byan integration test —
health-sweep.test.tsonly testsCRON_SECRETauth,and the pre-existing webhook fan-out isn't integration-tested there either.
The Opsgenie dispatch unit itself is fully covered by
opsgenie-dispatch.test.ts.Verification
Additional tests run (not in the plan's block, covering the new UI/route + dispatch wiring):
bun test src/routes/api/v1/health/opsgenie-test.test.ts --isolate— 6 passbun test src/lib/health/opsgenie-dispatch.test.ts src/routes/api/cron/__tests__/health-sweep.test.ts --isolate— 18 passTest plan
buildOpsgenieBodysnapshot + severity/alias/tags/details/description testsgetServerOpsgenieConfigenv-reader tests (empty/whitespace/region normalization)HEALTH_ALERT_OPSGENIE_API_KEYlocally and confirm "Send test" creates a real Opsgenie alert (not run — no sandbox API key available)https://claude.ai/code/session_01XpfaNdJZh4Rtr6cQm4DYqa