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-managermints/stores the scoped bot PAT and touches SOPS;sys-adminrunsgh auth login/ deploys the credential on bms-4. Adev-coderworker 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:writeonp24-infra) and is migrating off itsGH_TOKENPAT fallback under 4068 — not this issue; - the workflow-file push path already uses a separate
GH_TOKENPAT withWorkflows:writeviaGIT_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.
| # | Surface | Where | What mints it / TTL | Owner-level today? | In scope for #4815 Option 2? |
|---|---|---|---|---|---|
| A | Interactive gh CLI login | ~/.config/gh/hosts.yml under claude-runner; overridable by GH_TOKEN/GITHUB_TOKEN env | gh auth login (long-lived PAT) | YES — radieu owner PAT | ✅ YES — this is the target |
| B | git-over-HTTPS credential helper | git config credential.https://github.com.helper → bms-4/git-credential-gh-token.sh:20-31 | GitHub 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.env | Fallback still a PAT; App-token primary | ❌ No — owned by 4068 |
| C | Workflow-file push | GIT_ASKPASS helper, per docs/playbooks/worker-push-workflow-files.md | Separate GH_TOKEN fine-grained PAT (Workflows:write), from secrets/monitoring.env.sops | Scoped 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.
| Consumer | Location | gh / API surface | FG-PAT permission needed | Breaks under scoped bot? |
|---|---|---|---|---|
| Queue dispatcher — escalated-issue + PR scan | scripts/queue-dispatcher-loop.py (gh api --paginate issues/pulls) | read issues + PRs | Issues:read, Pull requests:read, Metadata:read | No |
| Worker pipeline — issue/PR lifecycle | infra/agent-prompts/worker-issue.md (gh issue view/edit/comment, gh pr create/edit/merge/comment) | read/write issues + PRs, labels, milestones | Issues:write, Pull requests:write, Contents:read, Metadata:read | No |
| Role-reroute + infra-task spawn | scripts/spawn-worker.sh, scripts/spawn-infra-task-worker.sh (gh issue view/edit/comment) | read/write issues | Issues:write | No |
telegram-claude-bot /ask handler | docs/telegram-claude-bot-operations.md; runs as claude-runner | git push (surface B) + gh for issue/PR context | Issues:write, Pull requests:write (git auth stays on B) | Verify (bot inherits the login; scoped bot is sufficient for its gh calls) |
| gmail daily agent | docs/gmail-tools-daily-agent-operations.md; runs as claude-runner | no direct gh api in current flow; inherits login | none / minimal | No |
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.sh | gh issue list/create | Issues:write | No |
| queue-exporter runner metric | monitoring/exporters/queue-exporter/app.py | previously 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 op | Scope it needs | Where it actually runs | Blocks the gh login switch? |
|---|---|---|---|
| Self-hosted runner (re)registration | repo Administration / registration-token | CI: .github/workflows/reregister-ionos-runners.yml (uses GITHUB_TOKEN); Ansible roles/github-runner runs from the control node, not the bms-4 login | No |
Push to .github/workflows/* | Workflows:write | Surface C — separate GH_TOKEN PAT via GIT_ASKPASS (worker-push-workflow-files.md) | No |
| Write GH Actions Secrets | Secrets:write | CI (credential-rotation.yml), not the bms-4 login; bms-4/sync-n8n-credentials.py note is unimplemented | No |
| Runner busy/idle metric | Administration:read | already removed from queue-exporter (#4369) | No |
| Create a new repo (e.g. p24-audit-log #3471) | Administration:write / owner | one-off owner action, not a recurring bms-4 login task | No (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.
- Pre-cutover audit (
sys-admin, read-only). On bms-4, rungh auth status(account + scopes only — never the token) and confirm the active login account. Enumerate the actualgh api/ghcall 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 theghlogin (they are — B is a git credential helper, C isGIT_ASKPASS). - Provision the scoped PAT (
secret-manager). On/for theAI-Dev-BMS4-1bot account, create a fine-grained PAT: Repository access =radieu/p24-infra(+radieu/et-operational-platformif that login serves et-op jobs); permissions Contents RW, Issues RW, Pull requests RW, Metadata R; noAdministration,Secrets,Workflows, org scopes. ConfirmAI-Dev-BMS4-1is a collaborator with write on those repos first (perdocs/gmail-tools-daily-agent-operations.md). - Store it in SOPS (
secret-manager). Add under a new distinct key name (e.g.GH_AUTH_LOGIN_TOKEN— do not overwrite the existingGH_TOKENinsecrets/n8n-bms4.env.sops, which is the surface-B/C PAT and must keep its own value). Confirm the SOPS map +secrets-sync.ymldistribution before choosing the file; canary-decrypt before commit. Reference key name only. - Dry-run in an isolated env (
sys-admin). Export the new token asGH_TOKENin 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 scratchgh issue commenton a test issue, agh pr create/gh pr mergedry path. All must succeed with the scoped token. - Cut over the login (
sys-admin).gh auth loginon bms-4 asclaude-runnerusing the bot PAT (gh auth login --with-tokenfrom the SOPS-sourced value via the safe extraction pattern — never echoed). Immediately re-run agit push/git pullon a scratch branch to prove surface B still works (it authenticates via the credential helper, independent of theghlogin). - Bake for one dispatch cycle. Watch a full queue cycle + one telegram
/ask+ the next gmail daily agent run. Confirm noghauth errors in/var/log/p24-infra-workers/…. Only after a clean cycle is the cutover “final”. - Rollback (any time before/after,
sys-admin).gh auth loginagain 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 statusshowsAI-Dev-BMS4-1, not radieu, on bms-4 -
gh issue/gh pr/gh apiread+write all succeed with the scoped token -
git push/git pullstill 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
| Risk | Likelihood | Mitigation |
|---|---|---|
| A surface-A consumer silently needed an owner-only scope not caught in §2/§3 | Low | Step-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 B | Medium (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 403 | Low | Step-2 pre-check collaborator + permission before minting |
| et-operational-platform worker jobs run under the same login and lose access | Low/Verify | Step-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_TOKEN | Low | Distinct 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
ghlogin.”
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:
- 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).
- Confirm the SOPS home + new key name
GH_AUTH_LOGIN_TOKENinsecrets/n8n-bms4.env.sops(vs. a per-role SOPS file perplan-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
--allowedToolsallowlists (#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_keyor 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-adminvia 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.