Skip to content

RFC: per-feedstock opt-in to trigger rebuild PRs when a build dependency updates #6641

Description

@trim21

RFC: per-feedstock opt-in to trigger rebuild PRs when a build dependency updates

PR: #6641

Background

Feedstocks that must be rebuilt when a build-only dependency (most commonly a compiler, e.g. ${{ compiler("go-nocgo") }}) updates currently have no way to be notified or rebuilt. Build dependencies produce no runtime requirement, and version updates never propagate to dependents, so nothing ever opens a rebuild PR for the downstream feedstock.

Concrete current example:

  • conda-forge/golangci-lint-feedstock 2.13.2 was built on 2026-08-27, when the newest go-nocgo was 1.26.x.
  • go-nocgo 1.27.1 was published on 2026-09-02.
  • Nothing anywhere triggers a rebuild of golangci-lint against go 1.27; the package on the channel stays built with the old toolchain until someone notices and manually bumps the build number.

Proposal

Per-feedstock opt-in in conda-forge.yml:

bot:
  rebuild_on_update:
    - go-nocgo

For each opted-in package the bot watches the newest version published on the conda-forge channel and opens a standard rebuild PR when it advances.

State lives in the feedstock repo

Each opted-in feedstock carries a small, bot-maintained state file (one line per watched package) that records the last version a rebuild was PRed for:

go-nocgo 1.26.2

The line is advanced only by rebuild PRs (merged => the file changes), so it is a monotonic, auditable marker: git history shows exactly what each rebuild was for, and maintainers can edit the file directly to force or suppress the next rebuild. Keeping the state in the feedstock rather than in bot-internal data means it survives bot-data resets/compactions and needs no migrator-specific bookkeeping.

If the state file is missing when the config first appears, the bot opens an initial rebuild PR that creates it at the then-current newest version (i.e. opting in while behind => immediate rebuild, which matches the motivating example). Maintainers who are already current can pre-create the file to opt in without an immediate PR.

Trigger

On each auto-tick run, for each opted-in package the bot fetches the newest published version on the channel and opens a rebuild PR when:

  • newest published version V > version recorded in the state file, and
  • V has been on the channel for at least 1 h.

The 1 h is a lower bound only — more delay is fine. The bot runs daily, so this is normally satisfied trivially; it mainly prevents firing while the package's platform matrix is still uploading. There is deliberately no per-platform "published everywhere" gate before opening the PR: waiting for every platform to carry V would delay every trigger by the slowest platform and adds per-platform bookkeeping for no practical gain, while the failure modes it would guard against are already covered by the 1 h lower bound plus the fact that these PRs are not auto-merged. The rebuild resolves the toolchain from the channel at build time, and a platform that permanently never receives V is a case for the maintainers (surfaced in the PR body, see below).

The rebuild PR

Standard bot rebuild: bump build.number, advance the state-file line to V, rerender. Title/commit like rebuild for go-nocgo 1.27.1. All existing machinery applies as-is: pr_limit, attempt throttling/backoff, check_solvable, bot-rerun.

Each PR is created from a deterministically named head branch on the bot's fork:

rebuild_on_update-<package>-<version>

The branch name is just a stable lookup key for the PR history, not a piece of state.

Two deliberate deviations:

  • These PRs never carry the [bot-automerge] slug, even when the feedstock sets bot.automerge: true. This keeps a human window in case the channel is not fully caught up, and is the safeguard for the residual "a platform permanently never receives V" case.
  • The PR body lists the platforms on which V was not yet found, so a maintainer deciding whether to merge can see the channel state at a glance.

Lifecycle semantics

Before opening a PR for candidate (package, V) the bot asks GitHub for that package's PR history by head branch:

GET /repos/conda-forge/{feedstock}-feedstock/pulls?state=all&head={bot_user}:rebuild_on_update-{package}-{V}
  • empty => never opened => create the PR;
  • an open PR exists => already in progress => do not re-open (conflicts are handled via bot-rerun);
  • only closed/merged PRs => treated as handled => skip that version.

PR records are permanent on GitHub (unlike branch existence, they survive branch deletion after a merge), so "opened and closed" is answered reliably without any bot-internal bookkeeping. Closing a rebuild PR without merging therefore does not cause a re-PR for the same version; the next version bump gets a fresh branch name and re-triggers naturally.

Implementation (all in conda-forge-bot; conda-smithy needs no release)

The schema home for bot: is conda-forge-bot itself: conda-smithy's bot: key is a free-form dict whose JSON schema is a live $ref to conda-forge-bot/main/conda_forge_tick/cf_tick_schema.json (CONDA_SMITHY_BOT_SCHEMA_URI overridable for tests). So:

  1. Add rebuild_on_update: list[str] to BotConfig in conda_forge_tick/config_schema.py and regenerate cf_tick_schema.json. Feedstocks must not add the key until this lands (their lint would otherwise fail); no conda-smithy release is required.
  2. New single-instance migrator in conda_forge_tick/migrators/, following the StaticLibMigrator pattern (one global instance; per-node participation decided from each node's own conda-forge.yml):
    • make_graph parses the state file into node attrs (reusing the existing clone/parse pipeline), so the migrator's filter can compare versions without cloning.
    • Channel lookups reuse the rattler gateway already used by the static-lib migrator; only opted-in feedstocks trigger them (a handful).
    • migrate writes the new version line and bumps the build number.
  3. Register the migrator in make_migrators.initialize_migrators and export it from conda_forge_tick/migrators/__init__.py (required for lazy deserialization).

Alternatives considered

  • Watching the go-feedstock manually and opening rebuild PRs by hand: no automation, easy to miss releases.
  • Per-feedstock scheduled workflow: not viable because smithy owns the rendered CI files.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions