Skip to content

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:

  1. Repeat the channel on each repository tier (explicit but verbose): Set the same channel value on each individual repository tier. This lets you customize mentions per repository.
  2. Use the "*" tier as a default (implicit but routes unlisted repositories): Set the channel only on the "*" tier, then omit channel from 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 → @channel
  • mentions: [] → no ping
  • mentions: [handles] → ping those handles

After (0.18)

  • mentions: key absent on a tier → inherit (an explicit org/repo tier 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):

  1. Repository tier (e.g., mappings.acme.api.reactions.approved) — highest priority
  2. Org "*" tier (e.g., mappings.acme.*.reactions.approved) — fallback for repository tier
  3. Global section (e.g., slack.reactions.approved or digest.schedule at 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.url
  • slack.base_url and github.base_url
  • cleanup.message_ttl_days

These infra-level settings are cluster-wide and apply to all repositories.

Next steps

  1. Rewrite your config.yaml mappings: section using the new tier schema.
  2. Run notifycat-config validate to check the migration.
  3. Deploy and restart the server.