Activates the review-bot and claude-oauth rotators added in #363.
Pin both CronJobs to secret-rotator@sha256:828bf486… — the build that carries woodpecker-secret mode (the old pinned digest on the sibling rotators predates it and would exit "unknown mode").
Unsuspend both (suspend: false). Safe now because the image has the mode, the OpenBao policies/roles + bootstraps are applied, and the org secrets already exist — so a run maintains rather than bootstraps.
Reconcile the claude-oauth BAO_PATH/BAO_FIELD to homelab/infrastructure/claude / setup-token (where the OAuth value is actually seeded), with the matching rotator-claude-oauth.hcl path change (already applied out-of-band).
Cadence once merged: review-bot mints+syncs review_bot_token quarterly; claude-oauth syncs claude_code_oauth_token daily (04:20). Both write the org-2 secrets the fleet inherits (homelab, org 1, keeps its own repo-level copies).
kustomize builds clean; no :latest remains; both suspend:false.
Activates the review-bot and claude-oauth rotators added in #363.
- **Pin** both CronJobs to `secret-rotator@sha256:828bf486…` — the build that carries `woodpecker-secret` mode (the old pinned digest on the sibling rotators predates it and would exit "unknown mode").
- **Unsuspend** both (`suspend: false`). Safe now because the image has the mode, the OpenBao policies/roles + bootstraps are applied, and the org secrets already exist — so a run maintains rather than bootstraps.
- **Reconcile** the claude-oauth `BAO_PATH`/`BAO_FIELD` to `homelab/infrastructure/claude` / `setup-token` (where the OAuth value is actually seeded), with the matching `rotator-claude-oauth.hcl` path change (already applied out-of-band).
Cadence once merged: review-bot mints+syncs `review_bot_token` quarterly; claude-oauth syncs `claude_code_oauth_token` daily (04:20). Both write the **org-2** secrets the fleet inherits (homelab, org 1, keeps its own repo-level copies).
kustomize builds clean; no `:latest` remains; both suspend:false.
Pin both CronJobs to the built secret-rotator image that carries woodpecker-secret
mode (sha256:828bf486…), flip suspend:false, and reconcile the claude-oauth
BAO path to homelab/infrastructure/claude#setup-token (where the OAuth value is
actually seeded) — with the matching policy update (applied out-of-band).
Bootstraps + policies/roles are already applied and the org secrets are live, so
the rotators now maintain them on schedule: review-bot mints+syncs review_bot_token
quarterly; claude-oauth syncs claude_code_oauth_token daily.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🤖Automated review — model claude-sonnet-5 (complexity: normal), commit a9e1dfc85a4e88b544bec9b2c1c9ddb645a0c975.
Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.
Summary
This PR pins both review-bot and claude-oauth secret-rotator CronJobs to the secret-rotator@sha256:828bf486… digest, flips suspend: false on both, and reconciles the claude-oauth BAO_PATH/BAO_FIELD (+ matching OpenBao policy path) to homelab/infrastructure/claude#setup-token. The review-bot.yaml change is correct and consistent with the codebase's own secret-rotator binary (woodpecker-secret mode reads BAO_PATH#BAO_FIELD, default field token; sync_woodpecker_secret() in secret-rotator/src/main.rs:371-407). The claude-oauth.yaml change, however, drops the CronJob's spec: key while removing the old suspend-comment block, leaving suspend/schedule/timeZone/jobTemplate/etc. nested under metadata: instead of spec: — a structurally invalid CronJob that will fail to apply. This is a blocking issue.
🔴 Blocking
kubernetes/infrastructure/openbao/rotation/claude-oauth.yaml:31-49 — the diff hunk (diff.patch lines 26-40) deletes the old -spec: line along with the stale suspend-comment but never re-adds spec:. Checking the file as it stands after this PR:
There is no top-level spec: key anywhere in this CronJob document (confirmed: grep -n '^spec:' claude-oauth.yaml returns nothing, vs. one match at review-bot.yaml:52). suspend, schedule, timeZone, concurrencyPolicy, and jobTemplate are all indented as siblings of labels under metadata:, i.e. they're being parsed as (invalid/unknown) ObjectMeta fields rather than CronJobSpec fields. metadata doesn't support these fields, and spec.schedule/spec.jobTemplate are required — so this manifest will be rejected by the API server (or by Flux's server-side apply) as invalid, not merely mis-scheduled. This defeats the entire purpose of the PR for claude-oauth: the rotator won't run at all, un-suspended or not.
Note the PR body's "kustomize builds clean" claim doesn't catch this: kustomize build only concatenates/patches YAML, it does not validate against the Kubernetes OpenAPI schema for CronJob, so this passes kustomize but would fail kubectl apply/Flux reconciliation. Fix: re-add spec: before suspend: false at the same indent level as metadata:, and indent everything from suspend through the end of jobTemplate one level deeper (matching review-bot.yaml's structure).
🟡 Suggestions
kubernetes/infrastructure/openbao/rotation/claude-oauth.yaml:5 — the file's top-of-file doc comment still says the owner seeds the value into OpenBao at infrastructure/claude-code-oauth#token by hand, but this PR moves the actual path to infrastructure/claude#setup-token (both in this file's env vars and in policies/rotator-claude-oauth.hcl). If the owner later re-reads this comment to figure out where to re-seed a refreshed claude setup-token value, they'd write to the old (now-unauthorized-by-policy) path. Worth a one-line follow-up to keep the header comment in sync with the reconciled path — the rest of the PR already updates this same fact everywhere else it appears (rotator-claude-oauth.hcl:9,27, claude-oauth.yaml:39-41,84-89).
🟢 Nits
None beyond the above.
Tests
No automated tests apply here (these are static Kubernetes manifests + OpenBao policy files, no CI schema validation appears to run over kubernetes/infrastructure/openbao/rotation/*.yaml in this repo based on what's in the diff). Given that, a kubectl apply --dry-run=server (or kubeconform/kubectl-validate against the CronJob schema) against changed manifests before merge would have caught the missing spec: key immediately — worth considering as a lightweight CI gate for this directory going forward, though that's outside the scope of this PR.
Recommendation
Request changes — 1 blocking issue (invalid CronJob YAML structure in claude-oauth.yaml breaks the very activation this PR is meant to deliver).
🤖 **Automated review** — model `claude-sonnet-5` (complexity: normal), commit `a9e1dfc85a4e88b544bec9b2c1c9ddb645a0c975`.
> Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review.
---
## Summary
This PR pins both `review-bot` and `claude-oauth` secret-rotator CronJobs to the `secret-rotator@sha256:828bf486…` digest, flips `suspend: false` on both, and reconciles the claude-oauth `BAO_PATH`/`BAO_FIELD` (+ matching OpenBao policy path) to `homelab/infrastructure/claude#setup-token`. The `review-bot.yaml` change is correct and consistent with the codebase's own `secret-rotator` binary (`woodpecker-secret` mode reads `BAO_PATH#BAO_FIELD`, default field `token`; `sync_woodpecker_secret()` in `secret-rotator/src/main.rs:371-407`). The `claude-oauth.yaml` change, however, drops the CronJob's `spec:` key while removing the old suspend-comment block, leaving `suspend`/`schedule`/`timeZone`/`jobTemplate`/etc. nested under `metadata:` instead of `spec:` — a structurally invalid CronJob that will fail to apply. This is a blocking issue.
## 🔴 Blocking
- `kubernetes/infrastructure/openbao/rotation/claude-oauth.yaml:31-49` — the diff hunk (`diff.patch` lines 26-40) deletes the old `-spec:` line along with the stale suspend-comment but never re-adds `spec:`. Checking the file as it stands after this PR:
```
33 metadata:
34 name: claude-oauth-secret-sync
...
38 app.kubernetes.io/component: secret-rotator
39 # Live: image pinned to the build that carries `woodpecker-secret` mode, the
...
42 suspend: false
43 schedule: "20 4 * * *" # daily, Europe/Berlin
44 timeZone: Europe/Berlin
45 concurrencyPolicy: Forbid
46 successfulJobsHistoryLimit: 2
47 failedJobsHistoryLimit: 5
48 startingDeadlineSeconds: 3600
49 jobTemplate:
```
There is no top-level `spec:` key anywhere in this CronJob document (confirmed: `grep -n '^spec:' claude-oauth.yaml` returns nothing, vs. one match at `review-bot.yaml:52`). `suspend`, `schedule`, `timeZone`, `concurrencyPolicy`, and `jobTemplate` are all indented as siblings of `labels` under `metadata:`, i.e. they're being parsed as (invalid/unknown) `ObjectMeta` fields rather than `CronJobSpec` fields. `metadata` doesn't support these fields, and `spec.schedule`/`spec.jobTemplate` are required — so this manifest will be rejected by the API server (or by Flux's server-side apply) as invalid, not merely mis-scheduled. This defeats the entire purpose of the PR for claude-oauth: the rotator won't run at all, un-suspended or not.
Note the PR body's "kustomize builds clean" claim doesn't catch this: `kustomize build` only concatenates/patches YAML, it does not validate against the Kubernetes OpenAPI schema for CronJob, so this passes kustomize but would fail `kubectl apply`/Flux reconciliation.
**Fix:** re-add `spec:` before `suspend: false` at the same indent level as `metadata:`, and indent everything from `suspend` through the end of `jobTemplate` one level deeper (matching `review-bot.yaml`'s structure).
## 🟡 Suggestions
- `kubernetes/infrastructure/openbao/rotation/claude-oauth.yaml:5` — the file's top-of-file doc comment still says `the owner seeds the value into OpenBao at infrastructure/claude-code-oauth#token by hand`, but this PR moves the actual path to `infrastructure/claude#setup-token` (both in this file's env vars and in `policies/rotator-claude-oauth.hcl`). If the owner later re-reads this comment to figure out where to re-seed a refreshed `claude setup-token` value, they'd write to the old (now-unauthorized-by-policy) path. Worth a one-line follow-up to keep the header comment in sync with the reconciled path — the rest of the PR already updates this same fact everywhere else it appears (`rotator-claude-oauth.hcl:9,27`, `claude-oauth.yaml:39-41,84-89`).
## 🟢 Nits
None beyond the above.
## Tests
No automated tests apply here (these are static Kubernetes manifests + OpenBao policy files, no CI schema validation appears to run over `kubernetes/infrastructure/openbao/rotation/*.yaml` in this repo based on what's in the diff). Given that, a `kubectl apply --dry-run=server` (or `kubeconform`/`kubectl-validate` against the CronJob schema) against changed manifests before merge would have caught the missing `spec:` key immediately — worth considering as a lightweight CI gate for this directory going forward, though that's outside the scope of this PR.
## Recommendation
Request changes — 1 blocking issue (invalid CronJob YAML structure in `claude-oauth.yaml` breaks the very activation this PR is meant to deliver).
<!-- pr-review:sha=a9e1dfc85a4e88b544bec9b2c1c9ddb645a0c975 -->
The earlier edit that removed the stale suspend-comment block also dropped the
'spec:' line, leaving suspend/schedule/jobTemplate nested under metadata: — an
invalid CronJob that would be rejected by the API server (kustomize build does
not validate against the k8s schema, so it passed). Re-add spec: and sync the
top-of-file doc comment to the reconciled infrastructure/claude#setup-token
path. Caught by the review bot on this PR; verified with kubectl --dry-run=server.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🤖Automated review — model claude-opus-4-8 (complexity: complex), commit 0b4ccd66f8d85a956be5aa302288f38701591e68.
Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.
Summary
Activates the two woodpecker-secret-mode rotators (review-bot, claude-oauth) by
pinning their images to the digest that carries that mode, flipping suspend: false,
and reconciling the claude-oauth source path/field to homelab/infrastructure/claude#setup-token.
The change is internally consistent and verifiable against the rotator source in-tree.
No blocking issues.
🟡 Suggestions
Un-suspend depends on out-of-band OpenBao state that the repo can't prove. rotation/claude-oauth.yaml:43 and rotation/review-bot.yaml:56 go live, but a
successful run requires that (a) the reconciled policy rotator-claude-oauth.hcl
(path homelab/data/infrastructure/claude, policies/rotator-claude-oauth.hcl:36)
is actually re-applied to OpenBao, and (b) the OAuth value is seeded at the new
path/field. The policy file itself notes it is "NOT applied by Flux… apply ONCE by
hand" (policies/rotator-claude-oauth.hcl:12-18), and the PR body says this was
done out-of-band. Failure mode is benign, not silent-wrong: read_field errors on
a missing/empty source value (secret-rotator/src/bao.rs:71, used at main.rs:396), so a misconfigured path/field makes the 04:20 job fail rather
than overwrite the working org secret with a blank — but it will fail nightly until
the OpenBao side matches. Worth a one-line confirmation that both the new policy
and the seeded value are in place before merge, since neither is checkable from the
checkout.
claude-oauth still carries BOOTSTRAP_PATH/woodpecker-bot bootstrap (claude-oauth.yaml:83-84),
which is correct — sync_woodpecker_secret reads the woodpecker-bot token from it
(main.rs:374,393). No action; just confirming this wasn't left stale by the path
reconcile.
Tests
No automated tests, consistent with the rest of this manifests directory. I verified
the change by reading the rotator source instead: woodpecker-secret mode exists
(main.rs:39), reads BAO_PATH/BAO_FIELD (main.rs:376-377, BAO_FIELD default token), and bao::read_field maps the logical path to the kv-v2 API path {mount}/data/{sub} (bao.rs:72) — so homelab/infrastructure/claude resolves to homelab/data/infrastructure/claude, exactly the path the reconciled policy grants
read on. The digest 828bf48… is unique to these two rotators; the other rotators
carry older digests, matching the PR's claim that only this build has the mode. No
lingering claude-code-oauth references remain, and the only secret-rotator:latest
is the build target in .woodpecker/build-ci-images.yml:151, not a runtime image.
Recommendation
Looks reasonable — no blocking issues; comments above are optional (mainly: confirm
the out-of-band OpenBao policy re-apply + seeded value before merging, since the repo
can't verify them).
🤖 **Automated review** — model `claude-opus-4-8` (complexity: complex), commit `0b4ccd66f8d85a956be5aa302288f38701591e68`.
> Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review.
---
## Summary
Activates the two `woodpecker-secret`-mode rotators (review-bot, claude-oauth) by
pinning their images to the digest that carries that mode, flipping `suspend: false`,
and reconciling the claude-oauth source path/field to `homelab/infrastructure/claude#setup-token`.
The change is internally consistent and verifiable against the rotator source in-tree.
No blocking issues.
## 🟡 Suggestions
- **Un-suspend depends on out-of-band OpenBao state that the repo can't prove.**
`rotation/claude-oauth.yaml:43` and `rotation/review-bot.yaml:56` go live, but a
successful run requires that (a) the reconciled policy `rotator-claude-oauth.hcl`
(path `homelab/data/infrastructure/claude`, `policies/rotator-claude-oauth.hcl:36`)
is actually re-applied to OpenBao, and (b) the OAuth value is seeded at the new
path/field. The policy file itself notes it is "NOT applied by Flux… apply ONCE by
hand" (`policies/rotator-claude-oauth.hcl:12-18`), and the PR body says this was
done out-of-band. Failure mode is benign, not silent-wrong: `read_field` errors on
a missing/empty source value (`secret-rotator/src/bao.rs:71`, used at
`main.rs:396`), so a misconfigured path/field makes the 04:20 job **fail** rather
than overwrite the working org secret with a blank — but it will fail nightly until
the OpenBao side matches. Worth a one-line confirmation that both the new policy
and the seeded value are in place before merge, since neither is checkable from the
checkout.
- **claude-oauth still carries `BOOTSTRAP_PATH`/woodpecker-bot bootstrap** (`claude-oauth.yaml:83-84`),
which is correct — `sync_woodpecker_secret` reads the woodpecker-bot token from it
(`main.rs:374,393`). No action; just confirming this wasn't left stale by the path
reconcile.
## Tests
No automated tests, consistent with the rest of this manifests directory. I verified
the change by reading the rotator source instead: `woodpecker-secret` mode exists
(`main.rs:39`), reads `BAO_PATH`/`BAO_FIELD` (`main.rs:376-377`, `BAO_FIELD` default
`token`), and `bao::read_field` maps the logical path to the kv-v2 API path
`{mount}/data/{sub}` (`bao.rs:72`) — so `homelab/infrastructure/claude` resolves to
`homelab/data/infrastructure/claude`, exactly the path the reconciled policy grants
read on. The digest `828bf48…` is unique to these two rotators; the other rotators
carry older digests, matching the PR's claim that only this build has the mode. No
lingering `claude-code-oauth` references remain, and the only `secret-rotator:latest`
is the build target in `.woodpecker/build-ci-images.yml:151`, not a runtime image.
## Recommendation
Looks reasonable — no blocking issues; comments above are optional (mainly: confirm
the out-of-band OpenBao policy re-apply + seeded value before merging, since the repo
can't verify them).
<!-- pr-review:sha=0b4ccd66f8d85a956be5aa302288f38701591e68 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Activates the review-bot and claude-oauth rotators added in #363.
secret-rotator@sha256:828bf486…— the build that carrieswoodpecker-secretmode (the old pinned digest on the sibling rotators predates it and would exit "unknown mode").suspend: false). Safe now because the image has the mode, the OpenBao policies/roles + bootstraps are applied, and the org secrets already exist — so a run maintains rather than bootstraps.BAO_PATH/BAO_FIELDtohomelab/infrastructure/claude/setup-token(where the OAuth value is actually seeded), with the matchingrotator-claude-oauth.hclpath change (already applied out-of-band).Cadence once merged: review-bot mints+syncs
review_bot_tokenquarterly; claude-oauth syncsclaude_code_oauth_tokendaily (04:20). Both write the org-2 secrets the fleet inherits (homelab, org 1, keeps its own repo-level copies).kustomize builds clean; no
:latestremains; both suspend:false.🤖 Automated review — model
claude-sonnet-5(complexity: normal), commita9e1dfc85a4e88b544bec9b2c1c9ddb645a0c975.Summary
This PR pins both
review-botandclaude-oauthsecret-rotator CronJobs to thesecret-rotator@sha256:828bf486…digest, flipssuspend: falseon both, and reconciles the claude-oauthBAO_PATH/BAO_FIELD(+ matching OpenBao policy path) tohomelab/infrastructure/claude#setup-token. Thereview-bot.yamlchange is correct and consistent with the codebase's ownsecret-rotatorbinary (woodpecker-secretmode readsBAO_PATH#BAO_FIELD, default fieldtoken;sync_woodpecker_secret()insecret-rotator/src/main.rs:371-407). Theclaude-oauth.yamlchange, however, drops the CronJob'sspec:key while removing the old suspend-comment block, leavingsuspend/schedule/timeZone/jobTemplate/etc. nested undermetadata:instead ofspec:— a structurally invalid CronJob that will fail to apply. This is a blocking issue.🔴 Blocking
kubernetes/infrastructure/openbao/rotation/claude-oauth.yaml:31-49— the diff hunk (diff.patchlines 26-40) deletes the old-spec:line along with the stale suspend-comment but never re-addsspec:. Checking the file as it stands after this PR:spec:key anywhere in this CronJob document (confirmed:grep -n '^spec:' claude-oauth.yamlreturns nothing, vs. one match atreview-bot.yaml:52).suspend,schedule,timeZone,concurrencyPolicy, andjobTemplateare all indented as siblings oflabelsundermetadata:, i.e. they're being parsed as (invalid/unknown)ObjectMetafields rather thanCronJobSpecfields.metadatadoesn't support these fields, andspec.schedule/spec.jobTemplateare required — so this manifest will be rejected by the API server (or by Flux's server-side apply) as invalid, not merely mis-scheduled. This defeats the entire purpose of the PR for claude-oauth: the rotator won't run at all, un-suspended or not.Note the PR body's "kustomize builds clean" claim doesn't catch this:
kustomize buildonly concatenates/patches YAML, it does not validate against the Kubernetes OpenAPI schema for CronJob, so this passes kustomize but would failkubectl apply/Flux reconciliation.Fix: re-add
spec:beforesuspend: falseat the same indent level asmetadata:, and indent everything fromsuspendthrough the end ofjobTemplateone level deeper (matchingreview-bot.yaml's structure).🟡 Suggestions
kubernetes/infrastructure/openbao/rotation/claude-oauth.yaml:5— the file's top-of-file doc comment still saysthe owner seeds the value into OpenBao at infrastructure/claude-code-oauth#token by hand, but this PR moves the actual path toinfrastructure/claude#setup-token(both in this file's env vars and inpolicies/rotator-claude-oauth.hcl). If the owner later re-reads this comment to figure out where to re-seed a refreshedclaude setup-tokenvalue, they'd write to the old (now-unauthorized-by-policy) path. Worth a one-line follow-up to keep the header comment in sync with the reconciled path — the rest of the PR already updates this same fact everywhere else it appears (rotator-claude-oauth.hcl:9,27,claude-oauth.yaml:39-41,84-89).🟢 Nits
None beyond the above.
Tests
No automated tests apply here (these are static Kubernetes manifests + OpenBao policy files, no CI schema validation appears to run over
kubernetes/infrastructure/openbao/rotation/*.yamlin this repo based on what's in the diff). Given that, akubectl apply --dry-run=server(orkubeconform/kubectl-validateagainst the CronJob schema) against changed manifests before merge would have caught the missingspec:key immediately — worth considering as a lightweight CI gate for this directory going forward, though that's outside the scope of this PR.Recommendation
Request changes — 1 blocking issue (invalid CronJob YAML structure in
claude-oauth.yamlbreaks the very activation this PR is meant to deliver).🤖 Automated review — model
claude-opus-4-8(complexity: complex), commit0b4ccd66f8d85a956be5aa302288f38701591e68.Summary
Activates the two
woodpecker-secret-mode rotators (review-bot, claude-oauth) bypinning their images to the digest that carries that mode, flipping
suspend: false,and reconciling the claude-oauth source path/field to
homelab/infrastructure/claude#setup-token.The change is internally consistent and verifiable against the rotator source in-tree.
No blocking issues.
🟡 Suggestions
Un-suspend depends on out-of-band OpenBao state that the repo can't prove.
rotation/claude-oauth.yaml:43androtation/review-bot.yaml:56go live, but asuccessful run requires that (a) the reconciled policy
rotator-claude-oauth.hcl(path
homelab/data/infrastructure/claude,policies/rotator-claude-oauth.hcl:36)is actually re-applied to OpenBao, and (b) the OAuth value is seeded at the new
path/field. The policy file itself notes it is "NOT applied by Flux… apply ONCE by
hand" (
policies/rotator-claude-oauth.hcl:12-18), and the PR body says this wasdone out-of-band. Failure mode is benign, not silent-wrong:
read_fielderrors ona missing/empty source value (
secret-rotator/src/bao.rs:71, used atmain.rs:396), so a misconfigured path/field makes the 04:20 job fail ratherthan overwrite the working org secret with a blank — but it will fail nightly until
the OpenBao side matches. Worth a one-line confirmation that both the new policy
and the seeded value are in place before merge, since neither is checkable from the
checkout.
claude-oauth still carries
BOOTSTRAP_PATH/woodpecker-bot bootstrap (claude-oauth.yaml:83-84),which is correct —
sync_woodpecker_secretreads the woodpecker-bot token from it(
main.rs:374,393). No action; just confirming this wasn't left stale by the pathreconcile.
Tests
No automated tests, consistent with the rest of this manifests directory. I verified
the change by reading the rotator source instead:
woodpecker-secretmode exists(
main.rs:39), readsBAO_PATH/BAO_FIELD(main.rs:376-377,BAO_FIELDdefaulttoken), andbao::read_fieldmaps the logical path to the kv-v2 API path{mount}/data/{sub}(bao.rs:72) — sohomelab/infrastructure/clauderesolves tohomelab/data/infrastructure/claude, exactly the path the reconciled policy grantsread on. The digest
828bf48…is unique to these two rotators; the other rotatorscarry older digests, matching the PR's claim that only this build has the mode. No
lingering
claude-code-oauthreferences remain, and the onlysecret-rotator:latestis the build target in
.woodpecker/build-ci-images.yml:151, not a runtime image.Recommendation
Looks reasonable — no blocking issues; comments above are optional (mainly: confirm
the out-of-band OpenBao policy re-apply + seeded value before merging, since the repo
can't verify them).