Plan: Switch the interactive gh auth login on bms-4 to the scoped AI-Dev-BMS4-1 bot (C-R6(b) Option 2 / Phase 0)

Issue: #4815 Type: Code-change-design (plan only — no credential move in this PR) Status: Draft — iteration 1 Last updated: 2026-08-11 Author: Claude Opus 4.8 (worker session bms4-cw-1 on bms-4, role dev-coder) Supersedes for Phase 0 only: the single-paragraph Phase 0 in docs/plans/plan-4815-fleet-root-credential-isolation.md §4 — this document is the detailed switch-over design the owner requested on 2026-08-05 (“Pursue Option 2 first … design first, no same-day credential move”). It does not re-plan Phases 1a/1b/2 — those stay in the parent plan.

Related: #4724 (Option A --allowedTools — shipped, untouched here) · #4245 / #4065 / #4068 (git-over-HTTPS App-token migration — the other GitHub surface, out of scope) · docs/environments/ai-dev-bms4-1.md (target host/account ops doc) · docs/playbooks/worker-push-workflow-files.md (workflow-scope push path — separate PAT, unaffected) · docs/playbooks/gh-pat-rotation.md · docs/adr/002-claude-account-topology.md

Scope discipline / secrets. Design/scoping artefact only — no secret values, key and file names only. Every execution step is delegated: secret-manager mints/stores the scoped bot PAT and touches SOPS; sys-admin runs gh auth login / deploys the credential on bms-4. A dev-coder worker authored this and performs none of those steps inline.


0. Summary — recommendation

Switch only the interactive gh auth CLI login on the bms-4 claude-runner account from radieu’s personal owner-level PAT to a scoped fine-grained PAT owned by the AI-Dev-BMS4-1 bot account. Leave the two other GitHub surfaces alone:

  • the git-over-HTTPS credential helper (bms-4/git-credential-gh-token.sh) already mints 1-hour App-installation tokens (contents:write on p24-infra) and is migrating off its GH_TOKEN PAT fallback under 4068not this issue;
  • the workflow-file push path already uses a separate GH_TOKEN PAT with Workflows:write via GIT_ASKPASS (docs/playbooks/worker-push-workflow-files.md) — not this issue.

The consumer audit (§2) shows the interactive gh login is used almost entirely for gh issue / gh pr / gh api read + issues/PR write — all covered by a fine-grained PAT with Contents, Issues, Pull requests (read/write) + Metadata (read). The only operations that need scopes a scoped PAT lacks (self-hosted runner registration = repo Administration, GH Secrets write) do not run from the interactive bms-4 login — they run in CI with GITHUB_TOKEN, or were already removed (§3). So the cutover is low-risk and fully reversible with a single gh auth login.

This is the design. No credential is moved in this PR. Execution is gated on ARCH decision #4 (already the plan’s own gate) and delegated to secret-manager + sys-admin.


1. The three GitHub credential surfaces on bms-4 (only one is in scope)

Grounded against the host + repo. Conflating these is the main risk, so they are separated first.

#SurfaceWhereWhat mints it / TTLOwner-level today?In scope for #4815 Option 2?
AInteractive gh CLI login~/.config/gh/hosts.yml under claude-runner; overridable by GH_TOKEN/GITHUB_TOKEN envgh auth login (long-lived PAT)YES — radieu owner PAT✅ YES — this is the target
Bgit-over-HTTPS credential helpergit config credential.https://github.com.helperbms-4/git-credential-gh-token.sh:20-31GitHub App installation token, 1 h TTL, contents:write on p24-infra (bin/gh-app-token.sh); GH_TOKEN PAT fallback from /opt/p24-infra/bms-4/n8n-bms4-gh.envFallback still a PAT; App-token primary❌ No — owned by 4068
CWorkflow-file pushGIT_ASKPASS helper, per docs/playbooks/worker-push-workflow-files.mdSeparate GH_TOKEN fine-grained PAT (Workflows:write), from secrets/monitoring.env.sopsScoped already❌ No — already scoped, separate token

Load-bearing distinction: surfaces B and C are already off the owner PAT (App token / separately-scoped PAT). This issue is only surface A — the gh CLI login used by workers’ gh issue/gh pr calls, the telegram /ask handler, and the gmail daily agent. Switching A does not touch how git push/git pull authenticate (that is B) — verify this explicitly at cutover (§4 step 5) because it is the most common “it broke everything” misconception.


2. Consumer inventory — everything using surface A (the gh login)

Every consumer of the interactive gh CLI / GitHub API reachable from the bms-4 claude-runner login. Scope column = the fine-grained-PAT permission the bot account needs to keep it working.

ConsumerLocationgh / API surfaceFG-PAT permission neededBreaks under scoped bot?
Queue dispatcher — escalated-issue + PR scanscripts/queue-dispatcher-loop.py (gh api --paginate issues/pulls)read issues + PRsIssues:read, Pull requests:read, Metadata:readNo
Worker pipeline — issue/PR lifecycleinfra/agent-prompts/worker-issue.md (gh issue view/edit/comment, gh pr create/edit/merge/comment)read/write issues + PRs, labels, milestonesIssues:write, Pull requests:write, Contents:read, Metadata:readNo
Role-reroute + infra-task spawnscripts/spawn-worker.sh, scripts/spawn-infra-task-worker.sh (gh issue view/edit/comment)read/write issuesIssues:writeNo
telegram-claude-bot /ask handlerdocs/telegram-claude-bot-operations.md; runs as claude-runnergit push (surface B) + gh for issue/PR contextIssues:write, Pull requests:write (git auth stays on B)Verify (bot inherits the login; scoped bot is sufficient for its gh calls)
gmail daily agentdocs/gmail-tools-daily-agent-operations.md; runs as claude-runnerno direct gh api in current flow; inherits loginnone / minimalNo
Error-notification scripts (Discord + gh issue create)scripts/mongodb-rs0-drop-detector.sh, scripts/socat-supabase-watchdog.sh, scripts/et-lager-mongodb-backup.sh, scripts/check-vps-i1-uptime.sh, scripts/mongodb-restore-drill-cron.sh, scripts/check-p24-workflows-connection.sh, scripts/deploy-gmail-tools-credentials.shgh issue list/createIssues:writeNo
queue-exporter runner metricmonitoring/exporters/queue-exporter/app.pypreviously GET /actions/runners (Administration:read)N/A — already removed (#4369; replaced by node_exporter liveness)

Net: a single fine-grained PAT with Repository access = radieu/p24-infra (+ radieu/et-operational-platform if the same login serves et-op worker jobs — confirm at cutover) and permissions Contents (RW), Issues (RW), Pull requests (RW), Metadata (R) keeps every surface-A consumer working. Nothing on surface A needs Administration, Secrets, Workflows, or any org/owner scope.


3. Operations that need scopes a scoped bot PAT lacks — and why none block Phase 0

These are the reasons the account was owner-level historically. Each is shown to not run from the interactive bms-4 login:

Privileged opScope it needsWhere it actually runsBlocks the gh login switch?
Self-hosted runner (re)registrationrepo Administration / registration-tokenCI: .github/workflows/reregister-ionos-runners.yml (uses GITHUB_TOKEN); Ansible roles/github-runner runs from the control node, not the bms-4 loginNo
Push to .github/workflows/*Workflows:writeSurface C — separate GH_TOKEN PAT via GIT_ASKPASS (worker-push-workflow-files.md)No
Write GH Actions SecretsSecrets:writeCI (credential-rotation.yml), not the bms-4 login; bms-4/sync-n8n-credentials.py note is unimplementedNo
Runner busy/idle metricAdministration:readalready removed from queue-exporter (#4369)No
Create a new repo (e.g. p24-audit-log #3471)Administration:write / ownerone-off owner action, not a recurring bms-4 login taskNo (do it as owner ad-hoc if ever needed)

Conclusion: the switch is safe precisely because the bms-4 interactive login only ever does issue/PR/read work. The privileged paths are already elsewhere. This is the fact that must be re-confirmed by sys-admin during the pre-cutover audit (§4 step 1) before the login is changed.


4. Switch-over runbook (for the delegated secret-manager + sys-admin execution issue)

Not executed here — this is the design the implementation issue will follow. Ordered, each step reversible until step 6.

  1. Pre-cutover audit (sys-admin, read-only). On bms-4, run gh auth status (account + scopes only — never the token) and confirm the active login account. Enumerate the actual gh api/gh call sites from §2 and confirm none require a scope beyond §2’s permission set on this host. Confirm surface B (git-credential-gh-token.sh) and surface C are independent of the gh login (they are — B is a git credential helper, C is GIT_ASKPASS).
  2. Provision the scoped PAT (secret-manager). On/for the AI-Dev-BMS4-1 bot account, create a fine-grained PAT: Repository access = radieu/p24-infra (+ radieu/et-operational-platform if that login serves et-op jobs); permissions Contents RW, Issues RW, Pull requests RW, Metadata R; no Administration, Secrets, Workflows, org scopes. Confirm AI-Dev-BMS4-1 is a collaborator with write on those repos first (per docs/gmail-tools-daily-agent-operations.md).
  3. Store it in SOPS (secret-manager). Add under a new distinct key name (e.g. GH_AUTH_LOGIN_TOKEN — do not overwrite the existing GH_TOKEN in secrets/n8n-bms4.env.sops, which is the surface-B/C PAT and must keep its own value). Confirm the SOPS map + secrets-sync.yml distribution before choosing the file; canary-decrypt before commit. Reference key name only.
  4. Dry-run in an isolated env (sys-admin). Export the new token as GH_TOKEN in a throwaway shell (not the worker service env yet) and exercise the representative calls: gh issue list, gh pr list, gh api repos/radieu/p24-infra (read), a scratch gh issue comment on a test issue, a gh pr create/gh pr merge dry path. All must succeed with the scoped token.
  5. Cut over the login (sys-admin). gh auth login on bms-4 as claude-runner using the bot PAT (gh auth login --with-token from the SOPS-sourced value via the safe extraction pattern — never echoed). Immediately re-run a git push/git pull on a scratch branch to prove surface B still works (it authenticates via the credential helper, independent of the gh login).
  6. Bake for one dispatch cycle. Watch a full queue cycle + one telegram /ask + the next gmail daily agent run. Confirm no gh auth errors in /var/log/p24-infra-workers/…. Only after a clean cycle is the cutover “final”.
  7. Rollback (any time before/after, sys-admin). gh auth login again with the prior owner credential (kept available until step 6 is confirmed clean). No other artefact changes, so rollback is a single command.

Verification checklist (implementation issue must tick all):

  • gh auth status shows AI-Dev-BMS4-1, not radieu, on bms-4
  • gh issue/gh pr/gh api read+write all succeed with the scoped token
  • git push/git pull still work (surface B unaffected)
  • Workflow-file push path (surface C) unchanged and still uses its own PAT
  • One clean queue dispatch cycle + telegram /ask + gmail daily run with no auth error
  • Prior owner credential retained until the clean cycle, then it may be de-scoped/rotated

5. Regression risks

RiskLikelihoodMitigation
A surface-A consumer silently needed an owner-only scope not caught in §2/§3LowStep-1 audit + step-4 isolated dry-run surface it before the login changes; rollback is one command
Someone assumes switching the gh login also changes git push auth and “fixes”/breaks surface BMedium (conceptual)§1 + step-5 explicit git push proof; documented that B/C are independent
AI-Dev-BMS4-1 lacks write-collaborator on a needed repo → gh pr/gh issue 403LowStep-2 pre-check collaborator + permission before minting
et-operational-platform worker jobs run under the same login and lose accessLow/VerifyStep-2 include et-op in repo access if that login serves et-op jobs; confirm at audit
New SOPS key collides with / overwrites the surface-B/C GH_TOKENLowDistinct key name (GH_AUTH_LOGIN_TOKEN); never overwrite GH_TOKEN

DB schema changes: no.


6. ARCH GATE — decision already recorded; one confirmation remains

The parent plan’s §6 decision #4 governs this Phase 0:

“Phase 0 bot-PAT scope + SOPS home; confirm no owner-scope automation (runner registration, org ops) depends on the current gh login.”

The owner ratified Option 2 as the first pass on 2026-08-05. This document answers the confirmation decision #4 asked for: §3 shows no owner-scope automation depends on the interactive bms-4 login (runner registration = CI; secrets = CI; workflow push = separate PAT). What remains for the human/secret-manager is only:

  1. Approve the exact fine-grained permission set in §2 (Contents/Issues/PRs RW + Metadata R) and the repo-access list (p24-infra ± et-operational-platform).
  2. Confirm the SOPS home + new key name GH_AUTH_LOGIN_TOKEN in secrets/n8n-bms4.env.sops (vs. a per-role SOPS file per plan-per-role-worker-credentials.md).

On that confirmation, open the delegated implementation issue (secret-manager + sys-admin) following §4.


7. Out of scope (explicit)

  • Do not touch the --allowedTools allowlists (#4724) — separate control, stays.
  • Do not re-plan the git-over-HTTPS App-token path (surface B, 4068) or the workflow-file push PAT (surface C) — both already off the owner PAT.
  • Do not move vps_root_key or the age key here — that is Phase 1b of the parent plan.
  • No credential is minted, moved, or SOPS file touched in this PR. This is the design; execution is delegated to secret-manager + sys-admin via a follow-up implementation issue once §6 is confirmed.

Role: dev-coder (design/scoping only; execution delegated to sys-admin + secret-manager). Auto-generated plan — review via /review-plan 4815 before opening the implementation issue.