fix(github-pat): make a global PAT rotation actually reach running agents (#1967) - #1979
Merged
Conversation
…ents (#1967) Rotating the global GitHub PAT in Settings reported success while updating nothing. Two independent gaps each fully defeated it, and both existed because the global path and the per-agent path (#1264) had drifted apart. **1. The eligibility gate was `.env`-shaped.** `_propagate_to_agent` passed `add_if_missing=False`, so an agent with no `/home/developer/.env` returned `skipped_no_pat`. That is *every* agent provisioned from a GitHub template, since those ship `.env.example` and no `.env`. On such a fleet 100% of agents skipped and the endpoint still answered `success: true`. **2. `.env` is not where git authenticates from.** Clones are created as `https://oauth2:<PAT>@github.com/<org>/<repo>.git`, and that URL is persisted in `.git/config` on the workspace volume. Rewriting `.env` changes nothing for the running `git` process — only re-templating the remote restores fetch/push before a restart, which the per-agent path already did and this one did not. Observed consequence: every global-PAT agent authenticating with a revoked token for as long as its container stayed up — 11–13 days — with nothing in the UI saying so. Both paths now route through one `_apply_pat_to_agent` body, so the next divergence has to be deliberate rather than accidental. **Eligibility moved to the git config, deliberately not to `add_if_missing=True`.** The issue suggests the unconditional flag; that would inject the global token into every running container, including agents that never touched GitHub — and this module's own docstring says the original gate existed to prevent exactly that. Gating on "Trinity manages a repo for this agent" targets precisely the population that was skipped, while an agent with no git config keeps the conservative behaviour: update an existing line, never create one. The two rules are a union, so an agent carrying `GITHUB_PAT` for the `gh` CLI with no managed repo is still rotated as before. **`remotes_updated` is reported separately from `updated`**, because `updated` alone overstates the fix: an agent whose `.env` was rewritten but whose remote was not is still broken for git until it restarts. Collapsing the two would repeat this issue's own mistake of reporting success for a partial effect. A zero-reach rotation, and a rotation that updated `.env` without the remote, both WARN in the platform log — the failure was silent for weeks, and a Settings panel nobody is watching during an incident is not sufficient. The issue's gap 3 ("propagation result never surfaced") is **already fixed on dev** — the Settings panel renders the full updated/failed/skipped breakdown. It was filed against 9f766d1. One residual remained and is fixed here: "0 of N applied" rendered success-green whenever nothing outright failed, so a rotation that reached no agent looked identical to one that worked. tests/unit/test_1967_global_pat_propagation.py — 13 checks, 7 of which fail against the pre-fix tree; the 6 that pass either way are the no-regression guards. Related to #1967 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…registry.json — keep all entries)
…build registry as dev entries + this PR's entry
vybe
approved these changes
Aug 4, 2026
vybe
left a comment
Contributor
There was a problem hiding this comment.
Validated via /validate-pr — well-scoped P1 fix with 7/13 regression tests failing pre-fix, per-agent/per-user PAT architecture preserved (skipped_per_agent_pat gate has its own test), no credential-spraying over-correction. I added the missing 'Fixes #1967' keyword, resolved the registry.json conflict (rebuilt as dev entries + this PR's entry, deduping a PRE-EXISTING duplicate test_181 entry that's been on dev since before today), and full CI re-ran green (initial run was cancelled by a transient runner issue; rerun all-green). Follow-ups filed: #1995 (stale per-agent PAT copy + github-sync.md drift).
This was referenced Aug 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Rotating the global GitHub PAT in Settings reported success while updating nothing. Agents kept authenticating with the revoked token —
git fetch/pushand theghCLI all failing — for as long as their containers stayed up. On the reporting fleet, 11–13 days.Two independent gaps, each of which fully defeated the rotation on its own. Both existed for the same reason: the global path and the per-agent path (#1264) had drifted apart.
1. The eligibility gate was
.env-shaped.Returns
skipped_no_patwhen/home/developer/.envis absent. That is every agent provisioned from a GitHub template, since those ship.env.exampleand no.env. On such a fleet 100% of agents skipped — and the endpoint still answeredsuccess: true.2.
.envis not where git authenticates from.Clones are created as
https://oauth2:<PAT>@github.com/<org>/<repo>.git, and that URL is persisted in.git/configon the workspace volume. Rewriting.envchanges nothing for the runninggitprocess. Only re-templating the remote restores fetch/push before a restart — which the per-agent path already did and this one did not.So even with gap 1 fixed, a rotation still would not restore git access.
Fix
Both paths now route through one
_apply_pat_to_agentbody (.envwrite plus live remote rewrite), so the next divergence has to be deliberate rather than accidental. That drift is the bug; deduplicating it is the actual fix, not a tidy-up.The part I did differently from the issue, and why
The issue suggests
add_if_missing=Trueunconditionally. I did not do that. It would inject the global token into every running container, including agents that never touched GitHub — and this module's own docstring says the original gate existed to prevent exactly that. Trading a silent-skip bug for a credential-spraying one is not an improvement.Eligibility moved to "does Trinity manage a repo for this agent" (the issue's own alternative), which targets precisely the population that was being skipped:
.env(template-provisioned)skipped_no_pat❌.env.envonly)GITHUB_PATlineGITHUB_PATlineThe two rules are a union, not a replacement: an agent carrying
GITHUB_PATfor theghCLI with no Trinity-managed repo is still rotated exactly as before. That row has its own no-regression test, because "fix the gate" is the obvious place to accidentally drop a working case.remotes_updatedis reported separately fromupdatedupdatedalone overstates the fix: an agent whose.envwas rewritten but whose remote was not is still broken for git until it restarts. Collapsing the two would repeat this issue's own mistake — reporting success for a partial effect.Both degraded shapes now also WARN in the platform log (zero-reach, and updated-without-remote). The failure was silent for weeks; a Settings panel nobody is watching during an incident is not sufficient.
Gap 3 is already fixed on
devThe issue says the propagation result "is never surfaced". It is —
Settings.vuerenders the full updated/failed/skipped breakdown with per-agent reasons. The issue was filed against9f766d11.One residual did remain, and is fixed here:
0 of N appliedrendered success-green whenever nothing outright failed, so a rotation that reached no agent looked identical to one that worked. It now renders as an error, and the remote-vs-env gap gets its own warning line.Not addressed (deliberate)
The issue's "secondary observation" — an agent whose per-agent PAT holds a stale copy of the old global token is skipped forever and re-injected on every recreate. Detecting that needs a validity probe against the GitHub API per agent, which is a different feature (network calls in a rotation path, rate limits, a new failure mode) rather than part of this fix. Worth its own issue; flagging rather than silently expanding scope.
Verification
tests/unit/test_1967_global_pat_propagation.py— 13 checks, 7 of which fail against the pre-fix tree. The 6 that pass either way are the no-regression guards, which is the correct result for them.Also green:
test_1264_per_agent_pat_propagation,test_github_pat_fallback,test_github_pat_recreation,test_ent162_per_user_github_pat,test_models_centralized— 63 passed. The #1264 tests passing unchanged is the useful signal: the shared-body refactor did not alter the per-agent path's behaviour.Two structural guards are included because the drift is the root cause: one asserts both callers reach
_apply_pat_to_agent, one assertsadd_if_missing=Falseis not reintroduced on the global path.Acceptance criteria
.env.envwritten" from "remote fixed", and a zero-reach rotation is loudRelated to #1967 · follow-up to #211, #1264, #1574
🤖 Generated with Claude Code
Fixes #1967