Mirroring what notifier just shipped. Watcher is one uv table plus a
github-actions surface, with 21 of 23 runtime specifiers already capped — so
the policy is effectively in place here and only the thing that proposes
crossing the caps is missing.
Why
This repo caps its dependencies — nearly every specifier carries an upper
bound. That is deliberate and good, and it has one consequence that is easy to
miss: a cap makes new releases invisible to uv lock. Nothing proposes
crossing it. The bound sits there until a person remembers to look, and nobody
remembers. notifier hit this and fixed it in
CannObserv/notifier#33:
Dependabot is what turns "a version wall nobody crosses" into a reviewed,
CI-gated diff.
The first batch landed there last week —
notifier#45 is the triage
of all eight PRs, including one that mattered (a markdown renderer carrying
four CWE-400 quadratic-DoS fixes that had been sitting unreachable behind a
<4 cap for months).
What notifier ships
Two files, both small.
.github/dependabot.yml — the whole config, comments included, since each
one records a decision that cost something to learn:
# Proposes the bumps the 0.x cap policy makes invisible to `uv lock`.
# Every cap sits one minor below a wall by design: crossing it takes a
# reviewed diff, and this file is what produces that diff instead of a memory.
version: 2
updates:
- package-ecosystem: "uv"
directories:
- "/"
schedule:
interval: "weekly"
# Touch a specifier only when the new release falls outside it. Floors in
# these tables mean "oldest supported" and must not ratchet on every bump;
# the default `auto` resolves to `widen` for libraries, and `widen` is
# unsupported for uv (dependabot-core#15290).
versioning-strategy: increase-if-necessary
groups:
# Dev tooling carries most of the churn (ruff alone ships minors roughly
# monthly); one grouped PR keeps the bot readable. Runtime dependencies
# stay individual PRs — those deserve their own review.
dev-tools:
dependency-type: "development"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
A guard test (tests/ci/test_dependabot.py in notifier). Worth the twenty
lines: a mistyped ecosystem name or a bad directories entry stops producing
PRs without erroring anywhere a person looks. The bot just goes quiet, and
quiet reads like "no updates available". Assert the ecosystems, the directory
list, versioning-strategy, and the group.
Two blockers to clear first — notifier had neither
notifier's CI uses no secrets, no cloud auth and no private index, so its
Dependabot PRs went green on the first try. This repo differs in exactly the
way that matters, twice. Both apply to every ecosystem, including
github-actions, because ci.yml is the gate on any Dependabot PR.
1. Dependabot runs have their own variable store
ci.yml authenticates to GCP with vars.GCP_WIF_PROVIDER and each job
requests id-token: write. Dependabot-triggered runs do not read the
Actions secret/variable store — they read the separate Dependabot store
(Settings → Secrets and variables → Dependabot). Until GCP_WIF_PROVIDER
is mirrored there with this repo in its access list, every Dependabot PR is
red at the auth step.
Confirm before writing any config, and while you are there confirm that OIDC
(id-token: write) is actually granted on Dependabot-triggered runs in this
org — if it is not, the wheelhouse cannot be synced for those PRs at all and
the gate needs rethinking (a workflow_run path, or accepting a manual re-run
per PR). A red bot is worse than no bot: red PRs get ignored, and then the
real one gets ignored too.
2. The private wheelhouse
co-core and co-core-aio are declared runtime dependencies that resolve
only from find-links = ["./.wheelhouse"], a directory that holds one
.gitkeep in git and is populated by scripts/sync_wheelhouse.py from
gs://co-gcs-pypi before any uv command.
Dependabot's uv updater checks the repo out and resolves the whole graph in its
own container. It will not run that script and has no GCS credentials, so it
should be expected to fail to resolve and error on every uv update. That is
a Dependabot-side failure, visible under Insights → Dependency graph →
Dependabot, not a red PR.
Options, roughly in order of effort: serve the GCS bucket as a PEP 503 index
over HTTPS and declare it as a registries: entry with a Dependabot secret;
or leave the uv ecosystem out and take the github-actions half now. ignore:
does not help — resolution still needs the package even when it is not being
updated.
Suggested order
- Mirror
GCP_WIF_PROVIDER into the Dependabot variable store; confirm a
Dependabot-triggered run can authenticate.
- Add
.github/dependabot.yml with the github-actions entry only. It
needs no package resolution, so it is the honest test of blocker 1 on its
own. This repo pins actions/checkout@v5 and astral-sh/setup-uv@v5; both
are two majors behind, so expect PRs immediately.
- Add the guard test.
- Only then decide about the uv ecosystem, with blocker 2 answered.
Repo-specific notes
Dependabot will not move the notifier-client pin
[tool.uv.sources]
notifier-client = { git = "ssh://git@github-notifier/CannObserv/notifier.git", subdirectory = "clients/python", rev = "v0.2.1" }
A tool.uv.sources git rev is not something the uv updater bumps, so this pin
stays wherever it is set no matter how the config is written. Two things follow:
- The pin is behind. notifier's SDK is at 0.3.1; this repo is pinned to
v0.2.1. In between: 0.3.0 documented AuthErrorDetail and the widened
return types from notifier#22, and 0.3.1 dropped python-dateutil from the
client's dependencies and regenerated against a newer OpenAPI generator.
- Upstream has not tagged those releases. notifier carries only
v0.2.0
and v0.2.1 as tags, so there is currently no v0.3.1 to point this rev at
even if you wanted to. Raised upstream in
notifier#49, which is
about what the SDK's version is supposed to mean; tagging is the part that
blocks this repo.
Neither is a reason to delay the config — it is a reason not to expect the bot
to cover this dependency. Whatever cadence you settle on for the notifier
client stays manual.
The ci.yml step that rewrites this SSH source to HTTPS is also worth keeping
in mind: a Dependabot-triggered run has to clone that source too, so it is a
second thing to confirm works under the Dependabot token, alongside blocker 1.
One uncapped runtime specifier
html5lib>=1.1 is the only runtime dependency without an upper bound. Worth
capping in the same pass so the config and the policy it serves agree.
versioning-strategy matters here
Floors like co-core[extract]>=0.10,<0.11 mean "oldest supported".
increase-if-necessary touches a specifier only when the new release falls
outside it; the default auto would ratchet the floor on every bump, and for
uv it resolves to widen, which is unsupported (dependabot-core#15290).
What the first batch looks like
From notifier's, so you can budget for it:
- Expect roughly one PR per capped dependency that has moved, all at once.
notifier got eight. Six were mergeable after reading the changelog; one
needed a service restart and a smoke check; one needed real work.
- The grouped dev-tools PR is the one that needs work. Linter and
code-generator majors change output, and the bump alone lands red. In
notifier's case ruff 0.16 began formatting Python code blocks inside
Markdown by default, and an OpenAPI generator bump rewrote every generated
model. Both were fixed by pushing commits onto the Dependabot branch —
allowed, and it stops the bot rebasing under you.
- Merge one at a time. Every PR that touches the root table rewrites
uv.lock, so merging one invalidates the rest. Comment @dependabot rebase
and let it catch up; Dependabot may also close and re-open a grouped PR under
a new number when the group's contents change mid-flight.
- Read the changelog, not the diff. The valuable finding in notifier's
batch was a security fix five minors back that the version number did not
advertise. The rest were routine.
Mirroring what notifier just shipped. Watcher is one uv table plus a
github-actionssurface, with 21 of 23 runtime specifiers already capped — sothe policy is effectively in place here and only the thing that proposes
crossing the caps is missing.
Why
This repo caps its dependencies — nearly every specifier carries an upper
bound. That is deliberate and good, and it has one consequence that is easy to
miss: a cap makes new releases invisible to
uv lock. Nothing proposescrossing it. The bound sits there until a person remembers to look, and nobody
remembers. notifier hit this and fixed it in
CannObserv/notifier#33:
Dependabot is what turns "a version wall nobody crosses" into a reviewed,
CI-gated diff.
The first batch landed there last week —
notifier#45 is the triage
of all eight PRs, including one that mattered (a markdown renderer carrying
four CWE-400 quadratic-DoS fixes that had been sitting unreachable behind a
<4cap for months).What notifier ships
Two files, both small.
.github/dependabot.yml— the whole config, comments included, since eachone records a decision that cost something to learn:
A guard test (
tests/ci/test_dependabot.pyin notifier). Worth the twentylines: a mistyped ecosystem name or a bad
directoriesentry stops producingPRs without erroring anywhere a person looks. The bot just goes quiet, and
quiet reads like "no updates available". Assert the ecosystems, the directory
list,
versioning-strategy, and the group.Two blockers to clear first — notifier had neither
notifier's CI uses no secrets, no cloud auth and no private index, so its
Dependabot PRs went green on the first try. This repo differs in exactly the
way that matters, twice. Both apply to every ecosystem, including
github-actions, becauseci.ymlis the gate on any Dependabot PR.1. Dependabot runs have their own variable store
ci.ymlauthenticates to GCP withvars.GCP_WIF_PROVIDERand each jobrequests
id-token: write. Dependabot-triggered runs do not read theActions secret/variable store — they read the separate Dependabot store
(Settings → Secrets and variables → Dependabot). Until
GCP_WIF_PROVIDERis mirrored there with this repo in its access list, every Dependabot PR is
red at the auth step.
Confirm before writing any config, and while you are there confirm that OIDC
(
id-token: write) is actually granted on Dependabot-triggered runs in thisorg — if it is not, the wheelhouse cannot be synced for those PRs at all and
the gate needs rethinking (a
workflow_runpath, or accepting a manual re-runper PR). A red bot is worse than no bot: red PRs get ignored, and then the
real one gets ignored too.
2. The private wheelhouse
co-coreandco-core-aioare declared runtime dependencies that resolveonly from
find-links = ["./.wheelhouse"], a directory that holds one.gitkeepin git and is populated byscripts/sync_wheelhouse.pyfromgs://co-gcs-pypibefore anyuvcommand.Dependabot's uv updater checks the repo out and resolves the whole graph in its
own container. It will not run that script and has no GCS credentials, so it
should be expected to fail to resolve and error on every uv update. That is
a Dependabot-side failure, visible under Insights → Dependency graph →
Dependabot, not a red PR.
Options, roughly in order of effort: serve the GCS bucket as a PEP 503 index
over HTTPS and declare it as a
registries:entry with a Dependabot secret;or leave the uv ecosystem out and take the
github-actionshalf now.ignore:does not help — resolution still needs the package even when it is not being
updated.
Suggested order
GCP_WIF_PROVIDERinto the Dependabot variable store; confirm aDependabot-triggered run can authenticate.
.github/dependabot.ymlwith thegithub-actionsentry only. Itneeds no package resolution, so it is the honest test of blocker 1 on its
own. This repo pins
actions/checkout@v5andastral-sh/setup-uv@v5; bothare two majors behind, so expect PRs immediately.
Repo-specific notes
Dependabot will not move the notifier-client pin
A
tool.uv.sourcesgit rev is not something the uv updater bumps, so this pinstays wherever it is set no matter how the config is written. Two things follow:
v0.2.1. In between: 0.3.0 documentedAuthErrorDetailand the widenedreturn types from notifier#22, and 0.3.1 dropped
python-dateutilfrom theclient's dependencies and regenerated against a newer OpenAPI generator.
v0.2.0and
v0.2.1as tags, so there is currently nov0.3.1to point this rev ateven if you wanted to. Raised upstream in
notifier#49, which is
about what the SDK's version is supposed to mean; tagging is the part that
blocks this repo.
Neither is a reason to delay the config — it is a reason not to expect the bot
to cover this dependency. Whatever cadence you settle on for the notifier
client stays manual.
The
ci.ymlstep that rewrites this SSH source to HTTPS is also worth keepingin mind: a Dependabot-triggered run has to clone that source too, so it is a
second thing to confirm works under the Dependabot token, alongside blocker 1.
One uncapped runtime specifier
html5lib>=1.1is the only runtime dependency without an upper bound. Worthcapping in the same pass so the config and the policy it serves agree.
versioning-strategymatters hereFloors like
co-core[extract]>=0.10,<0.11mean "oldest supported".increase-if-necessarytouches a specifier only when the new release fallsoutside it; the default
autowould ratchet the floor on every bump, and foruv it resolves to
widen, which is unsupported (dependabot-core#15290).What the first batch looks like
From notifier's, so you can budget for it:
notifier got eight. Six were mergeable after reading the changelog; one
needed a service restart and a smoke check; one needed real work.
code-generator majors change output, and the bump alone lands red. In
notifier's case ruff 0.16 began formatting Python code blocks inside
Markdown by default, and an OpenAPI generator bump rewrote every generated
model. Both were fixed by pushing commits onto the Dependabot branch —
allowed, and it stops the bot rebasing under you.
uv.lock, so merging one invalidates the rest. Comment@dependabot rebaseand let it catch up; Dependabot may also close and re-open a grouped PR under
a new number when the group's contents change mid-flight.
batch was a security fix five minors back that the version number did not
advertise. The rest were routine.