Changelog¶
All notable changes to OpsSignal are listed here, newest first. The format
follows Keep a Changelog and versions
follow Semantic Versioning. Pre-releases (-beta.N)
may still change config keys, the Python API and the SQLite schema.
The Python packages use PEP 440 spelling for the same version, so
1.0.0-beta.1 is 1.0.0b1 on PyPI.
Unreleased¶
Changes merged to main that are not in a release yet. They will ship in
the next tag.
1.0.0-beta.2 - 2026-10-21¶
Second beta: send one signal to several places at once, shape each
provider's message, and keep noisy routes quiet. Configs written for
1.0.0-beta.1 keep working unchanged, except that a misspelled key inside
a route's match is now reported instead of silently matching everything.
Added¶
- Fan-out delivery: a route can set
mode: fanoutto deliver to every provider it lists at once, each retried on its own, so a provider that already got the signal is never sent it again because another failed.failoverstays the default. A fan-out that reaches only some providers is reported as partial, with each provider's result, and the CLI exits 1 (#26). - Message templates per provider: an optional
templateblock under any provider replaces parts of its message with Jinja text, such as Slack'stext, PagerDuty'ssummary, Teams'title/text, Opsgenie'smessage/description, email'ssubject/body, console's line, or the whole webhook JSONbody. Templates see every signal field plusmetadata.*. Syntax errors, unknown parts and unknown variables fail at load time; a template that fails at send time falls back to the built-in format, so the alert still goes out. Configs without templates send exactly what they did before. Python and the C library get this from the config, with no API change (#32). opssignal previewprints what each provider would be sent for a signal, without sending or recording it, andopssignal notify/previewtake--metadataas a JSON object (#32).- Dedup and rate limits per route:
dedup: 30msends each incident (dedup key) at most once per window, andrate_limit: {max: 10, per: 1h}caps how many signals a route sends. Held-back signals are stored with statusskipped. The history lives in the SQLite file, so it holds across processes on one host (cron, Airflow tasks, the CLI) and across restarts. A failed send never suppresses the next one. The CLI exits 0 for a suppressed signal and prints the reason (#31).
Changed¶
- Rust API:
ConsoleProvideris now a struct built withConsoleProvider::new(), theProvidertrait has apreviewmethod with a default, so existing implementations still compile, andDeliveryResultgains aPartialstatus with per-provider results. - The SQLite sink keeps one
deliveriesrow per signal and provider, so a signal's history shows which providers already have it. - PyPI pages for
opssignal,opssignal-aws,opssignal-gcpandopssignal-azureget a project description, links, keywords and classifiers (#25). PyPI fixes a version's metadata at its first upload, so this shows from the next release; the1.0.0b1pages stay without a description.
Fixed¶
- An unknown key inside a route's
match(such asenviroment: prod) is now a load-time error. It used to be ignored, so the route matched every signal (#31).
1.0.0-beta.1 - 2026-10-20¶
First public build, published to PyPI (pip install --pre opssignal) and
crates.io (opssignal-core, opssignal-cli).
Added¶
- Providers: Slack, Microsoft Teams, PagerDuty (Events API v2),
Opsgenie, email (SMTP, or Amazon SES with
opssignal[aws]), generic webhook and console. A route lists providers in order and delivery stops at the first one that succeeds, so later providers are fallbacks. Each provider gets up to 3 attempts with backoff. - Config sources: local file,
https://,s3://,gs://,az://, AWS AppConfig (appconfig://), Azure App Configuration (azappconfig://), and DynamoDB, Firestore and Cosmos DB items. - Live reload every 60 seconds (
SIGNAL_CONFIG_REFRESH_SECS); a broken edit is logged and skipped. Remote configs are cached locally and used when the source is unreachable. - Layered configs with
extends:or a comma-separatedSIGNAL_CONFIGlist: a platform default file plus per-team files. - Secrets:
${VAR},${aws-sm:...},${gcp-sm:...}and${azure-kv:...}references, cached in memory only. - Reliability: every signal is stored in SQLite as
pendingbefore delivery, then markeddeliveredorfailed. In-flight signals are flushed on exit (Rust drop, Pythonatexit, Csignal_flush). - Python:
notify()with full signal context, an Airflowfailure_callback, and a dbt adapter (run_results.jsonor adbtRunnercallback). - CLI:
opssignal notify, which exits non-zero when an alert was not delivered. - C ABI in
crates/ffifor C hosts and future bindings. - Packaging: a cloud-free base package with
opssignal[aws],[gcp],[azure]and[all]extras, and matching cargo features for the CLI and C library. abi3 wheels for Python 3.9+ on Linux (glibc and musl, x86_64 and aarch64), Windows x86_64 and macOS arm64 (the macOS wheel was added to1.0.0b1on 2026-10-21). The Rust crates need Rust 1.89+.
Known limitations¶
- No fan-out: to send one signal to two places, use two routes.
- Signals left
pendingorfailedare kept but not redelivered later. - SQLite is the only sink; a team config file can add routes but not remove inherited ones.