One personal GitHub OAuth token carried 276 Terraform Enterprise workspaces until it was revoked
One revoked personal OAuth token stopped runs across 276 Terraform Enterprise workspaces, according to a dev.to write-up. Nobody was paged and the team needed two days to notice, so it moved the workspaces onto a GitHub App owned by the org.
The Engineer · Build desk

Key figures: an OAuth app can hold at most 10 tokens per user, which the dev.to post gives as the most likely reason the token was revoked. Setting up the org-level GitHub App in TFE took about 15 minutes. Moving 276 workspaces takes at least 276 PATCH calls, one per workspace.
| Figure | Value | Note | Claim |
|---|---|---|---|
| Tokens an OAuth app can hold per user, at most | 10 tokens | post's likeliest cause of the revocation | 18 |
| GitHub App setup in TFE, about | 15 minutes | 9 | |
| PATCH calls to move 276 workspaces, at least | 276 calls | one per workspace | 16 |
What happened
- The error 'Repository doesn't exist or isn't accessible' read like a single-repo permissions problem, though the credential was dead for every repo.
- Re-authorising the OAuth connection restored runs straight away, with the same token ID and the same webhooks.
- The team eliminated rate limiting, repo permissions and SSO de-authorisation as causes, along with the possibility that the app was shared with the old instance.
- The org-level GitHub App took about 15 minutes to set up in TFE, with read access to contents and metadata and write access to commit statuses.
Why it matters
- exposure If the 10-token theory holds, re-authorising during a migration can revoke the connection production is using. Any TFE org that routes all its workspaces through one personal OAuth connection carries that risk.
- cost Creating the App is cheap. The bill is the migration: at least 276 PATCH calls, one per workspace, with no partial-update shortcut.
- decision Any team with auto-apply workspaces has to choose, workspace by workspace, whether an unreviewed apply of the branch head is acceptable at cutover.
A 200 on every webhook meant nothing looked wrong from GitHub's side [4]. The failure came in the next step, when TFE tried to download the code [4]. TFE has no alert for "I can't read GitHub", the post says [7]. An empty run list passes for a quiet afternoon [7].
The root cause is an inference. The team could not prove why GitHub revoked the token [17]. Its best explanation is a GitHub limit of 10 tokens per user for an OAuth app: each Reauthorize click creates a new token, and past 10 the oldest is silently revoked [18]. Migrations produce those clicks, on the old instance, on the new one, during testing, and again when the first attempt looks stuck [19]. The post gets to the limit by elimination [20]. For it to be right, the production token had to be the oldest of more than ten. In our view the remedy holds either way, because it takes the personal account out of the path [10].
Re-authorising was a stopgap, since it would break again the next time anyone clicked the button [8]. Creating the App was the short part [9]. Moving the workspaces was the bigger job [11], and the post counts about 275 workspaces before the fix and 276 in the move [3][11]. Each one takes a PATCH to its vcs-repo block that sets github-app-installation-id in place of the OAuth token ID [12]. The PATCH wants the whole block. Send less and the workspace loses its branch, submodule setting and tag regex [12].
The script's guard is the sound engineering here. Changing a workspace's VCS connection queues a run, and with auto-apply on, that run applies whatever is on the branch, unreviewed [13]. So the script skips auto-apply workspaces unless an allow flag is passed, and it has a dry-run mode [13]. It also skips workspaces with no VCS block and ones already switched, so a second pass leaves finished workspaces alone [13]. The CSV report records each old OAuth token ID, and --revert-from report.csv rolls workspaces back [14]. The team backed up every workspace's vcs-repo block before touching anything [14].
The migration has a personal-account step of its own. The API token has to come from a user who has authorised the GitHub App in TFE themselves [15]. If not, TFE throws the identical "Repository doesn't exist or isn't accessible" error, for a different cause [15]. The post does not describe adding an alert for a dead VCS credential.
What to watch
- Confirmation from GitHub, or from token audit data, that a 10-token revocation hit the production token. Until then the cause is an inference by elimination.
- Whether the team adds a zero-runs or failed-clone alert, since TFE has none built in for losing GitHub access, per the post.
- How many auto-apply workspaces the script skipped, and how the team switched them.
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence55
- Adoption
- Insufficient
- Hype gap−10
- Incentives
- Insufficient
- Confidence50
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
After the Terraform Enterprise cutover to EKS, two days later every pipeline in the organisation stopped producing runs at the same moment, and nobody got paged.
- [2]
The whole platform reached GitHub through a single OAuth token belonging to a single personal account, and GitHub had quietly revoked it.
- [3]
On paper the organisation had about 275 workspaces with dozens of owning teams, but in practice they all went through one token.
- [4]
Webhooks kept arriving and GitHub got a 200 for every push; the failure was in the next step, when TFE tried to download the code, with 'Failed to clone repository: Invalid username or token'.
- [5]
The error 'Repository doesn't exist or isn't accessible' showed up when someone saved workspace settings or when the projects pipeline created a new workspace; it reads like a permissions problem on a single repo, but the credential was dead for every repo.
- [6]
Re-authorising the OAuth connection fixed the problem straight away: same token ID, same webhooks, and runs went green.
- [7]
It took two days to notice. TFE has no alert for 'I can't read GitHub'; runs just did not start, and an empty run list does not look broken.
- [8]
Re-authorising would happen again the next time anyone clicked that button; the durable fix is a GitHub App owned by the organisation.
- [9]
Setting up the GitHub App in TFE took about 15 minutes: an org-level app with read access to contents and metadata, write access to commit statuses, read access to pull requests, and subscriptions to push, pull_request, create and delete, installed on all repositories.
- [10]
With the GitHub App, the org owns the connection, so nobody's personal account is in the path.
- [11]
Moving 276 workspaces onto the GitHub App was the bigger job.
- [12]
Pointing a workspace at the app takes one PATCH to its vcs-repo block, setting github-app-installation-id in place of the OAuth token ID; the whole block must be sent back or the branch, submodule setting and tag regex are lost.
- [13]
The migration script skips workspaces with no VCS block and those already switched, and skips auto-apply workspaces unless an allow flag is passed, because changing the VCS connection queues a run that, with auto-apply on, applies whatever is on the branch unreviewed; it also has a dry-run mode.
- [14]
The script writes a CSV report that includes the old OAuth token ID so --revert-from report.csv can roll back any workspace, and the team backed up every workspace's vcs-repo block before touching anything.
- [15]
The API token must belong to a user who has personally authorised the GitHub App in TFE; without that, TFE returns the same 'Repository doesn't exist or isn't accessible' error, with a completely different cause.
- [16]
At one PATCH per workspace, moving 276 workspaces takes at least 276 PATCH calls.
- [17]
The team could not prove why GitHub revoked the token.
- [18]
The post's most likely explanation is a GitHub limit: an OAuth app can hold at most 10 tokens per user; every Reauthorize click creates a new token, and once there are more than 10, GitHub silently revokes the oldest.
- [19]
During a migration you click Reauthorize a lot: once on the old instance, once on the new one, again when testing, and again when the first one looks stuck.
- [20]
The team ruled out rate limits, repository permissions, SSO de-authorisation, and the old instance sharing the app; what remained was the 10-token limit.
Get this ledger inside ChatGPT. Ask about any of our stories and the answer brings its figure, each claim’s verdict and the sources. Free, no account.
Sources
1 independent publisher whose own reporting we read for this story.
- dev.toOne Personal Token Was Holding Up 276 Terraform Workspaces
1 article · October 13, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.
Topics
- Infrastructure as CodeFollow
- GitHub OAuth and GitHub AppsFollow
- Terraform EnterpriseFollow