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:
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:
- 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.
- 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.
- 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.
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-feedstock2.13.2 was built on 2026-08-27, when the newestgo-nocgowas 1.26.x.go-nocgo 1.27.1was published on 2026-09-02.Proposal
Per-feedstock opt-in in
conda-forge.yml: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:
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:
V> version recorded in the state file, andVhas 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
Vwould 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 receivesVis 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 toV, rerender. Title/commit likerebuild 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:
The branch name is just a stable lookup key for the PR history, not a piece of state.
Two deliberate deviations:
[bot-automerge]slug, even when the feedstock setsbot.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 receivesV" case.Vwas 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:bot-rerun);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'sbot:key is a free-form dict whose JSON schema is a live$reftoconda-forge-bot/main/conda_forge_tick/cf_tick_schema.json(CONDA_SMITHY_BOT_SCHEMA_URIoverridable for tests). So:rebuild_on_update: list[str]toBotConfiginconda_forge_tick/config_schema.pyand regeneratecf_tick_schema.json. Feedstocks must not add the key until this lands (their lint would otherwise fail); no conda-smithy release is required.conda_forge_tick/migrators/, following theStaticLibMigratorpattern (one global instance; per-node participation decided from each node's ownconda-forge.yml):make_graphparses the state file into node attrs (reusing the existing clone/parse pipeline), so the migrator'sfiltercan compare versions without cloning.migratewrites the new version line and bumps the build number.make_migrators.initialize_migratorsand export it fromconda_forge_tick/migrators/__init__.py(required for lazy deserialization).Alternatives considered