0.18 Migration: Per-Repository Mappings Tiers¶
Breaking change¶
Notifycat 0.18 restructures the mappings: section from a per-org schema to a per-repository-tier schema. This is a manual, breaking change with no automated converter. You must rewrite config.yaml before upgrading. The new schema is more flexible and allows per-repository channel and mention customization within an org.
Schema transformation¶
Before (0.17 and earlier)¶
Each org held a single channel, optional mentions list, and a repositories key that was either a list or the wildcard "*":
mappings:
acme:
channel: C0123ABCDE
mentions:
- "<@U0123ALICE>"
- "<@U0456BOB>"
repositories:
- api
- web
- platform
After (0.18+)¶
Each org now contains zero or more named tier keys (repository names) plus an optional "*" tier. Each tier sets its own channel and mentions:
mappings:
acme:
api:
channel: C0123ABCDE
mentions:
- "<@U0123ALICE>"
- "<@U0456BOB>"
web:
channel: C0456FGHIJ
platform:
channel: C0789KLMNO
"*":
channel: C0DEFAULT00
Wildcard transformation¶
A single-org repositories: "*" entry becomes a "*" tier under that org:
Before:
beta:
channel: C0456FGHIJ
repositories: "*"
After:
beta:
"*":
channel: C0456FGHIJ
Sharing a Channel: The Trade-off¶
In 0.17, you could share a single channel across multiple repositories by listing them all under one repositories entry. In 0.18, you have two options:
- Repeat the channel on each repository tier (explicit but verbose): Set the same
channelvalue on each individual repository tier. This lets you customize mentions per repository. - Use the
"*"tier as a default (implicit but routes unlisted repositories): Set the channel only on the"*"tier, then omitchannelfrom individual repository tiers so they inherit. Important trade-off: the"*"tier also catches any repository in the org that you do NOT list explicitly. If acme/internal-tool is not defined as a tier, a webhook from acme/internal-tool will match the"*"tier and route to the channel defined there. This is powerful for catch-all orgs but means you cannot have a shared-channel group that is also an allowlist.
Example: three repositories sharing a channel but not catching others:
Since the catch-all is baked into "*", you must repeat the channel:
acme:
api:
channel: C0SHARED00
web:
channel: C0SHARED00
platform:
channel: C0SHARED00
Example: whole-org default:
Any acme/* repository that is not defined explicitly falls back to "*":
acme:
api:
channel: C0API0000 # explicit channel for api
web:
channel: C0API0000 # explicit channel for web
"*":
channel: C0DEFAULT00 # catch-all for unlisted repositories
Mentions behavior¶
Before (0.17)¶
mentions:key absent → @channelmentions: []→ no pingmentions: [handles]→ ping those handles
After (0.18)¶
mentions:key absent on a tier → inherit (an explicitorg/repotier inherits from that org's*tier). The final fallback, when no tier sets mentions at all, is @channel (<!channel>).mentions: []→ no ping (same as 0.17)mentions: [handles]→ ping those handles (same as 0.17)mentions: null→ rejected (same as 0.17)
Key difference: inheritance is now explicit. If you want every repository in an org to ping the same people, set mentions only on the "*" tier; all other tiers inherit it.
Worked example¶
Before¶
acme:
channel: C0SHARED
mentions:
- "<@UTEAM>"
repositories:
- api
- web
- platform
After (Option 1: Repeat channel, inherit mentions)¶
acme:
api:
channel: C0SHARED
web:
channel: C0SHARED
platform:
channel: C0SHARED
"*":
mentions:
- "<@UTEAM>"
After (Option 2: Explicit per-repository, different mentions)¶
acme:
api:
channel: C0SHARED
mentions:
- "<@UAPI>"
web:
channel: C0SHARED
mentions:
- "<@UWEB>"
platform:
channel: C0SHARED
mentions:
- "<@UTEAM>"
CLI and Tools¶
The notifycat-config validate and notifycat-config list commands operate on the new per-repository-tier schema unchanged. No changes to their invocation or behavior — just the YAML shape they read.
notifycat-config list # prints new tier format
notifycat-config validate # validates new tier format
notifycat-config validate org/repo # validates one entry by repository name
The lock file (config.lock) is regenerated on first boot under 0.18.
Per-repository Behavioral Overrides¶
Each repository tier can override behavioral settings that would otherwise come from the global config.yaml section. This lets individual repositories use different reaction emojis, handle AI reviews differently, or post their digest on a custom schedule.
Overridable keys¶
The following keys can be set on a per-repository tier (and inherit from the org/* tier or global section when omitted):
- Reactions:
reactions.enabled,reactions.new_pr,reactions.merged_pr,reactions.closed_pr,reactions.approved,reactions.commented,reactions.request_change,reactions.bot_review - Reviews:
reviews.ignore_ai_reviews,reviews.dependabot_format - Digest:
digest.enabled,digest.schedule
Inheritance Precedence¶
Per-repository behavioral overrides follow this inheritance chain (most-specific wins):
- Repository tier (e.g.,
mappings.acme.api.reactions.approved) — highest priority - Org
"*"tier (e.g.,mappings.acme.*.reactions.approved) — fallback for repository tier - Global section (e.g.,
slack.reactions.approvedordigest.scheduleat the top level) — lowest priority
If a key is absent at all three levels, the built-in default applies.
Multi-Schedule Digests¶
When different repositories in the same organization use different digest.schedule values, the scheduler registers them separately and may post the digest multiple times per day to the same channel. For example, if delta/api posts at 9am and delta/web posts at 5pm (both to #delta-prs), that channel receives two digest posts daily. This is by design: each repository-tier schedule runs independently.
Settings NOT overridable per-repository¶
The following settings apply globally and cannot be overridden per-repository:
server.*(address, logging)database.urlslack.base_urlandgithub.base_urlcleanup.message_ttl_days
These infra-level settings are cluster-wide and apply to all repositories.
Next steps¶
- Rewrite your
config.yamlmappings:section using the new tier schema. - Run
notifycat-config validateto check the migration. - Deploy and restart the server.