Multi-Account Renovate CE Rollout — Implementation Plan
Multi-Account Renovate CE Rollout — Implementation Plan
Section titled “Multi-Account Renovate CE Rollout — Implementation Plan”For agentic workers: REQUIRED SUB-SKILL: Use dev-flow:subagent-driven-development (recommended) or dev-flow:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Expand the single in-cluster Mend Renovate CE to manage the seanb4t and holomush accounts alongside fzymgc-house, via a global config.js baseline + a widened autodiscover filter, with forks excluded and seanb4t curated by App-install scope.
Architecture: One config-bearing PR to this cluster repo (Task 1–2) followed by two out-of-band GitHub App installs with live verification (Task 3–4). No new infrastructure, secrets, or Vault policy — the App-ID/JWT credential is account-agnostic, so the same pod/App/secret serve all three accounts. The global config.js carries only the generic, account-agnostic baseline; cluster-specific rules stay in selfhosted-cluster/.github/renovate.json (which overrides global for that repo).
Tech Stack: Mend Renovate CE (Helm chart mend-renovate-ce 14.6.2), ArgoCD multi-source Application, helm/yq/yamllint/node for local validation, GitHub App installations, gh CLI for verification.
Design spec: docs/engineering/specs/2026-06-15-renovate-multi-account-design.md (design-reviewer READY round 2).
File structure
Section titled “File structure”| File | Action | Responsibility |
|---|---|---|
argocd/cluster-app/templates/renovate.yaml | Modify | Widen mendRnvAutoDiscoverFilter; add the renovate.config (global config.js) block to helm.valuesObject. |
No other files change. config.js ships inline through the chart’s renovate.config value (rendered to ConfigMap renovate-ce-config-js, mounted /usr/src/app/config.js); there is no standalone manifest. selfhosted-cluster/.github/renovate.json is intentionally left untouched (its cluster-specific rules override the global baseline for this repo). Tasks 3–4 are GitHub App installs (GitHub UI / API), not repo files.
Helm-templating caveat:
argocd/cluster-app/is itself a Helm chart, sorenovate.yamlis Helm-rendered. Theconfig.jsblock MUST NOT contain a{{sequence (Helm would try to interpret it). The baseline below contains none — preserve that property in any edit.
Task 1: Add global config.js baseline + widen autodiscover filter
Section titled “Task 1: Add global config.js baseline + widen autodiscover filter”Files:
-
Modify:
argocd/cluster-app/templates/renovate.yaml(therenovate:block ofhelm.valuesObject, currently lines ~27–36) -
Step 1: Widen the autodiscover filter
In argocd/cluster-app/templates/renovate.yaml, change the filter line (currently):
mendRnvAutoDiscoverFilter: "fzymgc-house/*" # scope; widen per-org at rolloutto:
# Comma = filter delimiter in env-var form → three independent filters. # Outer fence: nothing outside these three accounts is ever discovered. mendRnvAutoDiscoverFilter: "fzymgc-house/*,seanb4t/*,holomush/*"- Step 2: Add the
renovate.configglobal baseline
In the same renovate: block, immediately after the logFormat: json line, add a config: | block. The content is the global config.js (a module.exports = {...} JS file). Add it indented to match the other renovate.* keys (one level under renovate:):
# Global self-hosted config.js → ConfigMap renovate-ce-config-js, # mounted /usr/src/app/config.js. Carries the GENERIC, account-agnostic # baseline applied to every discovered repo across all 3 accounts. # Cluster-specific rules (component groups, manager file patterns) stay # in selfhosted-cluster/.github/renovate.json (repo config overrides global). # MUST NOT contain a '{{' sequence (this file is Helm-rendered). config: | module.exports = { // ---- Self-hosted / global controls ---- onboarding: true, // a repo is unmanaged until its onboarding PR merges onboardingConfig: { $schema: 'https://docs.renovatebot.com/renovate-schema.json', extends: ['config:recommended'], }, forkProcessing: 'disabled', // skip forks (strong default; a repo's own // renovate.json can still opt back in)
// ---- Shared baseline (flows down as defaults to every discovered repo) ---- extends: [ 'config:recommended', ':dependencyDashboard', ':semanticCommits', ':automergeDigest', ':automergePatch', ':automergeMinor', 'group:monorepos', 'group:recommended', 'workarounds:all', ], timezone: 'America/New_York', dependencyDashboard: true, dependencyDashboardTitle: '🤖 Dependency Dashboard', configMigration: true, osvVulnerabilityAlerts: true, prConcurrentLimit: 10, prHourlyLimit: 4, labels: ['dependencies', 'renovate'], automerge: true, automergeType: 'pr', platformAutomerge: true, rebaseWhen: 'auto', rangeStrategy: 'auto', semanticCommits: 'enabled', semanticCommitType: 'chore', semanticCommitScope: 'deps', vulnerabilityAlerts: { enabled: true, schedule: ['at any time'], automerge: true, minimumReleaseAge: null, commitMessageSuffix: '[SECURITY]', }, lockFileMaintenance: { enabled: true, schedule: ['before 4am on monday'], automerge: true, platformAutomerge: true, }, packageRules: [ { description: 'Major updates require manual review (no automerge)', matchUpdateTypes: ['major'], automerge: false, minimumReleaseAge: '7 days', prPriority: -1, }, { description: 'Auto-merge grouped minor and patch updates', matchUpdateTypes: ['minor', 'patch'], groupName: 'all non-major dependencies', groupSlug: 'all-minor-patch', automerge: true, automergeType: 'pr', platformAutomerge: true, minimumReleaseAge: '3 days', }, ], customManagers: [ { customType: 'regex', description: 'Update _VERSION variables in shell scripts and Makefiles', managerFilePatterns: ['/\\.sh$/', '/Makefile$/', '/\\.mk$/'], matchStrings: [ '# renovate: datasource=(?<datasource>\\S+) depName=(?<depName>\\S+)\\s+\\w+_VERSION\\s*=\\s*["\']?(?<currentValue>\\S+)["\']?', ], }, ], ignorePaths: [ '**/node_modules/**', '**/bower_components/**', '**/vendor/**', '**/examples/**', '**/test/**', '**/tests/**', '**/__tests__/**', ], };- Step 3: Lint the YAML
Run: yamllint -c .yamllint.yaml argocd/cluster-app/templates/renovate.yaml
Expected: no errors (exit 0). If line-length warnings fire on long preset lines, they are inside a block scalar (config: |) — yamllint does not enforce length inside block scalars, so this should pass clean.
- Step 4: Render the chart and confirm the Application + config.js render
Run:
helm template argocd/cluster-app | yq 'select(.kind == "Application" and .metadata.name == "renovate")' -Expected: the renovate Application renders without error, and under .spec.sources[0].helm.valuesObject.renovate you see both mendRnvAutoDiscoverFilter: fzymgc-house/*,seanb4t/*,holomush/* and the multi-line config string beginning module.exports = {.
- Step 5: Validate the embedded config.js is syntactically valid JavaScript
Extract the rendered config and check it with Node:
helm template argocd/cluster-app \ | yq 'select(.metadata.name == "renovate") | .spec.sources[0].helm.valuesObject.renovate.config' - \ > /tmp/renovate-config.jsnode --check /tmp/renovate-config.js && echo "config.js OK"Expected: config.js OK (exit 0). If node --check reports a syntax error, fix the config: | block in renovate.yaml and re-run Steps 4–5.
- Step 6: Confirm no
{{crept into the config block
Run: rg -n '\{\{' argocd/cluster-app/templates/renovate.yaml
Expected: no matches inside the config: | block (a match there would be Helm-interpreted and break rendering — Step 4 would already have failed, this is a fast guard).
- Step 7: Commit
Commit using VCS-appropriate commands per references/vcs-preamble.md. In this jj repo:
jj commit -m "feat(renovate): global config.js baseline + multi-account autodiscover filter [hl-rn4i]
Add the generic account-agnostic Renovate baseline as global config.js(forkProcessing disabled, onboarding on, automerge tiers, vuln alerts,dependency dashboard, _VERSION regex manager) and widen the autodiscoverfilter to fzymgc-house/seanb4t/holomush. No new infra/secrets.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"Task 2: Land the PR and verify fzymgc-house is unchanged
Section titled “Task 2: Land the PR and verify fzymgc-house is unchanged”Files: none (deployment + verification task)
- Step 1: Push the bookmark and open the PR
jj bookmark set worktree-renovate-multi-account -r @-jj git push -b worktree-renovate-multi-accountgh pr create --head worktree-renovate-multi-account \ --title "feat(renovate): multi-account CE — global config.js baseline + widened autodiscover [hl-rn4i]" \ --body "Implements docs/engineering/plans/2026-06-15-renovate-multi-account.md. Config-only: global config.js baseline + autodiscover filter widened to fzymgc-house/seanb4t/holomush. No new infra/secrets/Vault policy. Per-account App installs follow in separate rollout steps (holomush, then seanb4t)."Expected: PR created. Wait for merge to main (branch protection requires it).
- Step 2: After merge, confirm ArgoCD synced the renovate Application
Use the Kubernetes MCP (read-only) or kubectl context fzymgc-house:
kubectl --context fzymgc-house get application renovate -n argocd -o jsonpath='{.status.sync.status} {.status.health.status}{"\n"}'Expected: Synced Healthy.
- Step 3: Confirm the ConfigMap now carries the baseline
kubectl --context fzymgc-house get configmap renovate-ce-config-js -n renovate -o jsonpath='{.data.config\.js}' | head -5Expected: output begins module.exports = { and contains forkProcessing: 'disabled'.
- Step 4: Confirm CE restarted cleanly with the new config
kubectl --context fzymgc-house get pods -n renovatekubectl --context fzymgc-house logs -n renovate deploy/renovate-ce --tail=50 | rg -i 'error|fatal|config' | head -20Expected: pod 1/1 Running; logs show config loaded with no fatal/config errors. (If CE crashloops, the config.js is semantically invalid — roll back by reverting the PR; ArgoCD re-syncs the prior ConfigMap.)
- Step 5: Regression check — fzymgc-house behavior unchanged
The global baseline now merges under every fzymgc-house repo’s own config. Verify the richest repo (selfhosted-cluster, which has its own .github/renovate.json) is unaffected:
- Trigger a debug run or inspect the next scheduled run’s logs for
selfhosted-clusterin ClickStack/Loki (queryservice.namefor renovate, filter the repo), OR check the repo’s open Renovate PRs/Dependency Dashboard before vs. after. - Confirm: no change in group names, no newly-automerged majors, the
🤖 Dependency Dashboardissue is intact, no duplicate/conflicting PRs.
Expected: fzymgc-house PR cadence and grouping are visually unchanged. If the global baseline alters effective config (e.g. an unexpected new group), narrow the global packageRules and repeat from Task 1.
- Step 6: No commit (verification only). Record findings as a
bd noteon this task’s bead.
Task 3: Roll out holomush (org-wide install)
Section titled “Task 3: Roll out holomush (org-wide install)”Files: none (GitHub App install + verification)
- Step 1: Pre-flight — cut over from the Mend cloud Renovate app
holomush already has the Mend-hosted cloud Renovate app installed org-wide
(app_slug: renovate, repository_selection: all). Running CE alongside it
causes double-management (duplicate PRs, competing bots) — the same situation
the fzymgc-house epic resolved by uninstalling the cloud app.
Confirm the pre-existing cloud install:
gh api /orgs/holomush/installations --jq '.installations[] | select(.app_slug=="renovate") | "\(.app_slug) selection=\(.repository_selection)"'Expected: renovate selection=all. Uninstall (or suspend) the Mend cloud
Renovate app from holomush via GitHub → Settings → Installed GitHub Apps,
before installing CE. (Expect the cloud bot’s old artifacts — Dependency
Dashboard, open PRs — to go stale, as in the fzymgc-house cutover; close them
after CE takes over.)
- Step 2: Install the
fzymgc-renovateApp on holomush
In GitHub: install the fzymgc-renovate App (App ID 4059017) on the holomush org with scope All repositories (holomush has 1 repo; org-wide is fine).
Verify the CE App install replaced the cloud one:
gh api /orgs/holomush/installations --jq '.installations[].app_slug'Expected: the list now includes fzymgc-renovate and no longer includes the cloud renovate.
- Step 3: Confirm CE discovers holomush within the fence
After the next autodiscover cycle, check CE logs:
kubectl --context fzymgc-house logs -n renovate deploy/renovate-ce --since=15m | rg -i 'holomush'Expected: log lines showing holomush/holomush discovered/onboarding (matched by the holomush/* filter).
- Step 4: Verify the onboarding PR
gh pr list --repo holomush/holomush --author 'app/fzymgc-renovate' --json number,titleExpected: a “Configure Renovate” onboarding PR exists. Open it and confirm the proposed renovate.json (extends: ['config:recommended'] per onboardingConfig) is correct for a Go repo.
- Step 5: Merge the onboarding PR and confirm management begins
Merge the onboarding PR. After the next cycle, confirm Renovate opens dependency PRs (e.g. Go module updates) and the Dependency Dashboard issue appears.
Expected: dependency PRs follow the baseline (grouped minor/patch automerge, majors held for review).
- Step 6: No commit (rollout/verification). Record outcome as a
bd note.
Task 4: Roll out seanb4t (selected-repos install, forks excluded)
Section titled “Task 4: Roll out seanb4t (selected-repos install, forks excluded)”Files: none (GitHub App install + verification)
- Step 1: Install the App on a first batch of seanb4t source repos
In GitHub: install fzymgc-renovate on the seanb4t account with scope Only select repositories, choosing an initial small batch of source repos (e.g. start with the 5 that already have a renovate config: engram, weft, image-organizer-cli, example-movie-service, bootstrap-graphql-model-mutate). Do not select any fork.
Verify the selected set. The account-level installation endpoints require a
GitHub-App JWT (they 401/403 with an ordinary gh token), so verify per
repo instead — /repos/{owner}/{repo}/installation works with a normal token:
for r in engram weft image-organizer-cli example-movie-service bootstrap-graphql-model-mutate; do printf '%s -> ' "$r" gh api "/repos/seanb4t/$r/installation" --jq '.app_slug' 2>/dev/null || echo "NOT installed"doneExpected: each selected repo prints fzymgc-renovate. The authoritative
selected-repos list is also visible in the GitHub UI (Settings → Installed
GitHub Apps → fzymgc-renovate → Configure).
- Step 2: Confirm CE discovers only the selected source repos
kubectl --context fzymgc-house logs -n renovate deploy/renovate-ce --since=15m | rg -i 'seanb4t/'Expected: only the selected repos appear. No fork appears. Cross-check that none of the selected names is a fork:
gh repo list seanb4t --json name,isFork --jq '.[] | select(.isFork==true) | .name' > /tmp/seanb4t-forks.txt# none of these names should show up in the CE discovery logs above- Step 3: Explicitly verify the fork guard
Pick one seanb4t fork and confirm CE never processes it:
kubectl --context fzymgc-house logs -n renovate deploy/renovate-ce --since=1h | rg -i "$(head -1 /tmp/seanb4t-forks.txt)"Expected: either no match, or a log line indicating the fork was skipped (forkProcessing disabled). No PR is opened on any fork.
- Step 4: Verify onboarding for the selected repos
for r in engram weft image-organizer-cli example-movie-service bootstrap-graphql-model-mutate; do echo "== $r =="; gh pr list --repo seanb4t/$r --author 'app/fzymgc-renovate' --json number,title --jq '.[].title'doneExpected: repos without a prior config get an onboarding PR; repos that already have a renovate.json start managing directly (no onboarding PR needed). Confirm baseline behavior (grouped automerge, majors held).
- Step 5: Expand the install incrementally
Add further seanb4t source repos to the App’s selected set in batches, repeating Steps 2–4 per batch until the desired set is covered. Stop when membership matches intent.
- Step 6: No commit (rollout/verification). Record the final managed-repo set and any excluded repos as a
bd note(no-silent-caps: list what was deliberately left out).
Rollback
Section titled “Rollback”Single rollback lever for the config change: revert the Task 1/2 PR on main; ArgoCD re-syncs the prior (empty) ConfigMap and CE returns to fzymgc-house-only behavior. App installs (Tasks 3–4) are reverted independently by uninstalling the App from the affected account — no repo change needed.