Repository navigation
feat: run each project as its own git account - #1
Merged
Merged
Conversation
Adds git accounts (label, author identity, one forge host, a forge token or CLI login, optional SSH key) with a global default and project-scoped overrides. Account tokens use the settings secret store and are redacted for clients; editing accounts needs providers:manage.
GitAccounts resolves the project override, then the global default, and builds the environment that makes git and forge CLIs act as the account: author/committer, a host-scoped credential helper, SSH/HTTPS rewrites, the forge token variable, and removal of inherited tokens. Unusable or deleted accounts get a blocked overlay that fails commits, pushes and forge calls naming the account instead of falling back to the machine.
When a project has a git account, PullRequestService runs its forge calls with that account's token, which replaces the saved host token, GH_TOKEN, the gh login and the GitHub App sign-in on every supported forge. An unusable account fails closed naming it.
Stacked actions resolve the project's git account, check it against the push remote's host before anything is committed, and run the commit, push and PR step under it. The GitHub stack rebase accepts the account so rebased commits and their push use it. Hidden checkpoint commits keep the T3 Code identity.
… config Terminals and project setup scripts start with the project's git account; an unusable account opens the terminal with git blocked and a notice naming the account. RepoConfigSync writes the account identity into the repo's local config when the project opts in and restores the previous values when it opts out, unbinds or the account is deleted. Adds a git-account test lane on Linux and macOS runners.
GitVcsDriverCore.test.ts has macOS-only /private/var path failures that predate this work, so the lane runs only its git-account case.
Moves EnvironmentOverlay and applyEnvironmentOverlay next to the provider instance environment so provider adapters can apply a project's git account to the processes they spawn.
ProviderSessionManager resolves the thread's git account when it opens a session, hands the adapter the account overlay, and remembers the account fingerprint. A session reused under a different account is released and reopened; a shared session that serves other threads reports ProviderSessionAccountChangedError instead. Adapters must declare appliesAccountEnvironment, so one that can't run as the account is refused in bound projects rather than using the machine's identity.
…count When a shared provider session (Codex, OpenCode v2) runs as a different git account than the thread's project, turn start records the thread on a session of its own and starts again, so the turn runs as the right account without disturbing the other threads on the shared process.
Codex, Claude, Antigravity, Grok, Fx, the ACP registry, Pi, Muse and OpenCode (when it starts its own server) apply the git account overlay to the agent process and, for ACP agents, to client terminals, and declare appliesAccountEnvironment. Cursor runs in-process and OpenCode with an external or pooled server can't take one project's account, so bound projects refuse them.
Settings > Source Control lists git accounts with add, edit and remove (removal says which projects it blocks), editable with providers:manage. The project rows pick the account and opt into writing it to the repo's git config; at environment scope they set the default.
The mobile Source control page gets the Git account choice and the write-to-repo-config switch, with project overrides, plus a Git accounts section to add and remove accounts on a single environment. The account helpers move to client-runtime so web and mobile share them.
t3_project_git_account lists accounts and switches the calling thread's project to one. A switch first returns a phrase the user must type in the thread; the server applies it only after a user-authored web or mobile message with that phrase, so an agent can't rebind a project on its own. This works from the published mobile app with no new screens.
Drop the unused GitAccounts.list method and summary type, reuse launchIdentity's shellQuote for the failing credential helper, and cache gh login tokens for 30 seconds so each turn, terminal and forge call no longer spawns gh. Remove comments that restated the code. Claude Opus 5.5 via Claude Code (T3 Code)
A GitHub stack rebase in a project bound to a git account pushed with the account's token but committed as "T3 Code <noreply@t3.codes>", because the only caller never passed the account to cascadeRebaseStack. The forge account credential now carries the account's author name and email, and the stack action passes it through. Claude Opus 5.5 via Claude Code (T3 Code)
added 11 commits
November 1, 2026 01:09
Claude Opus 5.5 via Claude Code (T3 Code)
A project whose account can't be used blocked git everywhere, with no way through. A thread can now bypass the problem: it then runs as this machine's git and gh login, which the status names, until the account works again or its settings change. Other threads keep failing closed. A failed project-store read now resolves to a blocked identity instead of silently falling back to the machine login, and a remote on another host is part of the same check so it can be bypassed too. Claude Opus 5.5 via Claude Code (T3 Code)
Clients can now ask a thread's git account status, including the machine login it would fall back to, and turn a bypass on or off. Setting a bypass needs the source control write grant, like pushing does. The server now builds one GitAccounts instance shared by provider sessions, terminals, git actions and the RPC handlers, so a bypass set from a client reaches all of them. Claude Opus 5.5 via Claude Code (T3 Code)
…count A terminal kept the git account it started with, so a switch or a bypass only reached terminals opened afterwards. T3 terminals now put git and gh shims first on PATH. Each call sources the terminal's env file, which the server rewrites whenever settings or a thread's bypass change, then runs the real tool. Env files hold tokens, so they are owner-only, removed when the terminal closes, and cleared on start. A shell profile that puts another git ahead of the shims still starts on the right account but won't follow later switches. Claude Opus 5.5 via Claude Code (T3 Code)
Sending a message, committing, pushing or opening a pull request in a thread whose git account can't be used now asks first. The warning says why the account can't be used and which machine login the thread will use instead. Continuing turns on that thread's bypass. Opening a terminal shows the same warning without waiting, since open terminals pick the bypass up live. A badge next to the thread title shows the bypass and turns it off. The status also covers draft threads, which the server doesn't know yet, by passing their project. Claude Opus 5.5 via Claude Code (T3 Code)
…confirms The published mobile app can't show the new warning, so t3_project_git_account gains bypass and end_bypass. A bypass first returns the reason and the machine login the thread would use, plus a phrase; it applies only after the user types that phrase in their own message, the same rule as switching. Ending a bypass needs no confirmation, since it only restores the account. list now also reports whether the calling thread's account works. Adds an mcp-tool-use eval case; it was dry-run only, since no model keys are available here. Claude Opus 5.5 via Claude Code (T3 Code)
Mobile builds that include this now ask before sending, running a git action or opening a terminal in a thread whose git account can't be used, with the same wording as web. The git panel shows when a thread uses the machine login and taps back to the account. Older published builds keep the agent route from the previous change. Claude Opus 5.5 via Claude Code (T3 Code)
…count A remote like git@github-work:org/repo, the usual way to keep two accounts apart in ~/.ssh/config, or Azure's ssh.dev.azure.com was refused because its host didn't match the account's. The check now resolves an alias with ssh -G and maps known forge SSH hosts to their web host before comparing, and still refuses a remote it can't place. Claude Opus 5.5 via Claude Code (T3 Code)
Two projects on one host with different git accounts shared one viewer login, so filters like my pull requests and review requested could show the other account's work. Viewers are now resolved and cached per host and account; the list result still reports one viewer per host. Claude Opus 5.5 via Claude Code (T3 Code)
An agent could only learn which account a project commits as by calling the git account tool. t3_project_read now includes the effective account's id, label and whether it comes from the project or the default, never its credential. A deleted account keeps its id so the agent can say which one is missing. Claude Opus 5.5 via Claude Code (T3 Code)
Editing an account must leave its saved token alone, since the client only ever holds the redaction marker, and removing one must say which projects lose it first. Claude Opus 5.5 via Claude Code (T3 Code)
added 3 commits
November 1, 2026 05:15
Claude Opus 5.5 via Claude Code (T3 Code)
Claude Opus 5.5 via Claude Code (T3 Code)
The git account MCP tool and the project read's account field add 777 bytes to the tool list agents see, so the committed budget moves to the measured size. The git account settings now sort after the fetch interval in search, so a search for git still leads with it. Claude Opus 5.5 via Claude Code (T3 Code)
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
People with more than one forge account had no way to tell T3 Code which one a project belongs to. Agents committed with whatever name and email they chose, and pushes and PRs went out as whichever account was signed in on the machine, so switching meant signing out and back in (for example in GitHub Desktop) before each project.
Change
You can now add several git accounts and pick one per project, with a global default. Every agent session, terminal, setup script, and server-side commit, push, and PR in that project then runs as that account: its author name and email, its credentials, and its forge login. If a project's account can't be used, T3 Code warns first and only falls back to this machine's login for a thread you confirm.
ghlogin, plus an optional SSH key. Covers GitHub and Enterprise, GitLab, Forgejo/Gitea, Bitbucket, Azure DevOps, and GitCafe. Managed in Settings → Source control on web and desktop, and in mobile settings.GIT_CONFIG_COUNTentries, a host-scoped credential helper,url.insteadOf,core.sshCommand, and the forge's token variables), so two projects can run as different accounts at the same time without touching~/.gitconfig. An opt-in per-project switch also writesuser.name/user.emailinto the repo's own config, so terminals and GitHub Desktop match; turning it off restores the previous values.ghlogin missing, SSH key missing, remote on another host), sending a message, committing, pushing, opening a PR or opening a terminal shows a warning that says why and names the machine git author andghlogin the thread would use instead. Continuing turns on a bypass for that thread only, shown by a Machine git login badge that also turns it off. The bypass lives in server memory and ends when the account works again or its settings change. Without a bypass the server still fails closed, and a failed project-store read now counts as unusable instead of silently using the machine login.gitandghshims first onPATH. Each call sources a per-terminal env file (owner-only, removed on close) that the server rewrites when settings or a bypass change, so open terminals pick up switches without reopening. A shell profile that prepends its owngitkeeps the start-up account.git@github-work:org/repo(resolved withssh -G) and forge SSH hosts likessh.dev.azure.comcount as the account's host.t3_project_git_accountlets an agent list accounts, request a switch, or request a bypass. Both apply only after the user types the confirmation phrase in their own message from web or mobile, so an agent can't do either by itself. Newer mobile builds show the same warning natively.t3_project_readnow names the project's account and whether it comes from the project or the default.Session-settled decisions carried from planning: identity plus credentials plus forge login (user-directed, over identity-only); machine login or stored token (user-directed); all supported forges (user-directed, over GitHub-first); fail closed (user-approved, over warn-and-fall-back); global default plus project override (user-directed); restart before the next turn (user-approved, over interrupting); per-process enforcement plus opt-in repo config (user-directed); switching from the published app through an agent with user confirmation (user-directed).
Scope and approval
This is a fork feature requested by the fork's maintainer. It builds on what already exists (project-scoped settings, provider adapters, and per-forge credential lookups). Stripping agent co-author trailers is a separate global option and is not part of this PR.
Verification
gitrun checking that commits use the account's author and that a blocked account fails), session restart and moving off a shared session, the switch confirmation, repo-config write and restore, each forge's account credential, and terminal environments. Results: server suites 185/185 for the changed areas, forge packages 880/880, terminal 81/81..github/workflows/git-account-tests.yml) passed on earlier commits. A run on the latest commit is in progress.ghlogin; Confirm sent the message and showed the badge; clicking the badge ended the bypass; Cancel kept the draft unsent.check:changedon Windows: the only new failures were the agent context budget and settings search order, both fixed. The rest also fail on this Windows machine without these changes (SSH error classification, Claude attachments, worktree recovery, an Antigravity path check, an ACP shell test).agentContextBudget.jsonupdated); upstream CI would need theagent-context-increaselabel.Review findings
t3_project_readnow shows the account;t3_project_git_accounthandler tests and anevals/case added (dry-run only, no model keys here); project-settings doc updated.Review run
20261101-012503-261590d8; artifacts were kept locally at%TEMP%\compound-engineering-197609\ce-code-review\20261101-012503-261590d8.Claude Opus 5.5 via Claude Code, running in T3 Code.