Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
fef996b
feat(contracts): store per-project git accounts in server settings
getBoolean Nov 1, 2026
426a718
feat(server): resolve a project's git account into a launch identity
getBoolean Nov 1, 2026
df73bea
feat(source-control): run forge calls as the project's git account
getBoolean Nov 1, 2026
b8d4fcb
feat(git): commit, push and open PRs as the project's git account
getBoolean Nov 1, 2026
ad907b7
feat(server): run terminals and scripts as the git account, sync repo…
getBoolean Nov 1, 2026
cd4ef13
ci: scope the macOS git-account lane to account tests
getBoolean Nov 1, 2026
6e63f52
refactor(provider-core): share the git account environment overlay
getBoolean Nov 1, 2026
7d42cf5
feat(orchestration): open provider sessions as the project's git account
getBoolean Nov 1, 2026
c38e1ad
feat(orchestration): move a thread off a shared session on another ac…
getBoolean Nov 1, 2026
6e75b45
feat(providers): run agents as the project's git account
getBoolean Nov 1, 2026
82000ff
feat(web): manage git accounts and pick one per project
getBoolean Nov 1, 2026
68cdfed
feat(mobile): pick a project's git account and manage accounts
getBoolean Nov 1, 2026
48a6521
feat(mcp): let the user switch a project's git account from any client
getBoolean Nov 1, 2026
7258f34
docs: explain per-project git accounts
getBoolean Nov 1, 2026
6548b50
fix(mobile): keep the git account form compatible with the React comp…
Nov 1, 2026
2417a9a
refactor(server): simplify git account resolution
Nov 1, 2026
e047267
fix(review): apply review findings
Nov 1, 2026
85213f2
docs: describe what this fork adds in the README
Nov 1, 2026
eb09113
feat(server): let a thread bypass an unusable git account
Nov 1, 2026
56652bd
feat(contracts): read and set a thread's git account bypass over RPC
Nov 1, 2026
2083611
feat(terminal): shadow git and gh so open terminals follow the git ac…
Nov 1, 2026
0bee559
feat(web): warn before a thread runs without its git account
Nov 1, 2026
6e5badb
feat(mcp): let an agent bypass an unusable git account once the user …
Nov 1, 2026
ac33fe9
feat(mobile): warn before a thread runs without its git account
Nov 1, 2026
a20ff89
fix(server): accept SSH alias and forge SSH host remotes for a git ac…
Nov 1, 2026
1d78a76
fix(server): resolve the pull request viewer per git account
Nov 1, 2026
1aed7e7
feat(mcp): show a project's git account in t3_project_read
Nov 1, 2026
a4b1bb0
test(web): cover git account settings editing and removal
Nov 1, 2026
ad4754b
docs: explain the git account bypass, SSH aliases and terminal shims
Nov 1, 2026
7c96ef4
docs: update the fork summary for the git account bypass
Nov 1, 2026
c499b4e
chore: account for the git account tool text and search order
Nov 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions .github/workflows/git-account-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Git accounts inject git config, credential helpers and SSH commands that behave
# differently per OS shell, so their tests also run on macOS and Windows (ci.yml covers Linux only).
name: Git Account Tests

on:
pull_request:
paths:
- apps/server/src/gitAccounts/**
- apps/server/src/git/GitManager*.ts
- apps/server/src/vcs/GitVcsDriver*.ts
- apps/server/src/terminal/Manager*.ts
- apps/server/src/pullRequest/PullRequestService*.ts
- packages/source-control-*/src/server/**
- .github/workflows/git-account-tests.yml
push:
branches: [feat/project-git-accounts]

permissions:
contents: read

concurrency:
group: git-account-tests-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: Test (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v6
with:
sparse-checkout: |
/*
!/.repos/
sparse-checkout-cone-mode: false

- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version-file: package.json
cache: true
run-install: false

- name: Install
run: vp install

- name: Test server
working-directory: apps/server
run: >-
vp test run
src/gitAccounts
src/git/GitManager.test.ts
src/terminal/Manager.test.ts
src/project/ProjectSetupScriptRunner.test.ts
src/pullRequest/PullRequestService.test.ts
src/serverSettings.test.ts
src/auth/RpcAuthorization.test.ts

# The rest of this file has macOS-only path failures unrelated to accounts.
- name: Test driver env passthrough
working-directory: apps/server
run: vp test run src/vcs/GitVcsDriverCore.test.ts -t "environment the caller passes"

- name: Test source control packages
run: |
for forge in github gitlab forgejo bitbucket azure-devops gitcafe; do
(cd "packages/source-control-$forge" && vp test run src/server)
done

- name: Test shared settings
working-directory: packages/shared
run: vp test run src/projectSettings.test.ts

# Terminal shims are .cmd files on Windows, with their own quoting rules.
windows:
name: Test (windows-latest)
runs-on: windows-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v6
with:
sparse-checkout: |
/*
!/.repos/
sparse-checkout-cone-mode: false

- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version-file: package.json
cache: true
run-install: false

- name: Install
run: vp install

- name: Test git accounts
working-directory: apps/server
run: vp test run src/gitAccounts
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ T3 Code is an "agent harness control surface". It enables control of the agents

Works with your subscriptions on Claude Code, Codex, Cursor, Grok Build, OpenCode, Google Antigravity, and fx. If they're set up on your computer, T3 Code can control them.

## What this fork adds

This is a fork of [pingdotgg/t3code](https://github.com/pingdotgg/t3code). It adds:

- **A git account per project.** Add several git accounts (author name and email, plus a saved token or an existing `gh` login, and an optional SSH key), set a default, and pick a different one for any project. Agents, terminals, setup scripts, and T3 Code's own commits, pushes, and pull requests in that project use that account, so you don't have to sign out and back in when you switch projects. If the account can't be used, T3 Code warns first and only falls back to your machine's login for a thread you confirm. Open terminals follow account switches too. Works with GitHub, GitLab, Forgejo/Gitea, Bitbucket, Azure DevOps, and GitCafe. See [Use a different git account per project](./docs/user/source-control.md#use-a-different-git-account-per-project).

The installers and app stores below ship upstream T3 Code, which doesn't include these changes. To use them, build this fork from source (start at [docs/internals/overview.md](./docs/internals/overview.md)). With a server built from this fork, the published mobile app can still switch a project's account by asking an agent in a thread, and the switch only happens once you confirm it.

## "Wait, what are you selling me?"

Nothing. We built T3 Code because we wanted the best possible development experience with agents. We were inspired by existing solutions like the Codex desktop app, Conductor, Claude Desktop and Cursor Glass, but none met our bar.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ vi.mock("./components/AutoSettleDaysField", () => ({ AutoSettleDaysField: () =>
vi.mock("./components/SourceControlSignInSection", () => ({
SourceControlSignInSection: () => null,
}));
vi.mock("./components/GitAccountsSection", () => ({ GitAccountsSection: () => null }));
vi.mock("react-native-safe-area-context", () => ({ useSafeAreaInsets: () => ({ bottom: 0 }) }));
vi.mock("@react-navigation/native", () => ({ useNavigation: () => ({}) }));
vi.mock("../../state/session", () => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useNavigation } from "@react-navigation/native";
import { SettingsRow } from "./components/SettingsRow";
import { AuthSettingsWriteScope } from "@t3tools/contracts";
import { AuthProvidersManageScope, AuthSettingsWriteScope, GitAccountId } from "@t3tools/contracts";
import { readEnvironmentScope, useEnvironmentsWithScope } from "../../state/session";
import { ScreenScrollView as ScrollView } from "../../components/ui/ScreenScrollView";
import { AppText as Text } from "../../components/ui/AppText";
Expand Down Expand Up @@ -29,6 +29,7 @@ import { ListChoiceRow as SettingsChoiceRow } from "../../components/ui/ListChoi
import { SettingsSection } from "./components/SettingsSection";
import { SettingsSwitchRow } from "./components/SettingsSwitchRow";
import { SourceControlSignInSection } from "./components/SourceControlSignInSection";
import { GitAccountsSection } from "./components/GitAccountsSection";
import { SettingsProjectOverridesSection } from "./components/SettingsProjectOverridesSection";
import { useSettingsEnvironmentFilter } from "./settings-environment-filter";
import { useSettingsWriter } from "./useSettingsWriter";
Expand Down Expand Up @@ -61,7 +62,12 @@ const PAGE_PROJECT_KEYS: Record<SettingsPage, readonly ProjectScopedServerSettin
"sidebarAutoSettleOnMerge",
"sidebarAutoSettleAfterDays",
],
"source-control": ["defaultAutoPull", "removeAgentCreditsOnMerge"],
"source-control": [
"defaultAutoPull",
"removeAgentCreditsOnMerge",
"gitAccountId",
"gitAccountWritesRepoConfig",
],
worktrees: [
"newWorktreesStartFromOrigin",
"worktreeSubmodules",
Expand Down Expand Up @@ -178,6 +184,7 @@ function ServerSettingsDetail(props: { readonly page: SettingsPage }) {
const { selectedTargets, projectGroups, selectedProjectKey } = useSettingsEnvironmentFilter();
const selectedProject = projectGroups.find((group) => group.key === selectedProjectKey);
const writableEnvironments = useEnvironmentsWithScope(selectedTargets, AuthSettingsWriteScope);
const accountManagers = useEnvironmentsWithScope(selectedTargets, AuthProvidersManageScope);
const projectSelected = selectedProjectKey !== null;
const targets = resolveMobileSettingsTargets(
selectedTargets,
Expand Down Expand Up @@ -399,6 +406,50 @@ function ServerSettingsDetail(props: { readonly page: SettingsPage }) {
onValueChange={(value) => write({ removeAgentCreditsOnMerge: value })}
/>
</SettingsSection>
<SettingsSection
title="Git account"
trailing={
pendingWrites === 0 && isMixed("gitAccountId") ? (
<MixedValuesLabel projectSelected={projectSelected} />
) : null
}
>
{[
...(projectSelected ? [] : [{ id: null, label: "None", host: null }]),
...Object.entries(reference.settings.gitAccounts).map(([id, account]) => ({
id: GitAccountId.make(id),
label: account.label,
host: account.host,
})),
].map((choice, index) => (
<SettingsChoiceRow
key={choice.id ?? "none"}
label={choice.label}
description={
choice.host === null ? "Use this machine's git login." : choice.host
}
selected={!isMixed("gitAccountId") && uniform("gitAccountId") === choice.id}
separated={index > 0}
disabled={disabledFor("gitAccountId")}
onPress={() => write({ gitAccountId: choice.id })}
/>
))}
<SettingsSwitchRow
icon="doc.text"
label="Write to repo git config"
subtitle="Also set user.name and user.email in the repo. Turning it off restores the previous values."
value={uniform("gitAccountWritesRepoConfig")}
disabled={disabledFor("gitAccountWritesRepoConfig")}
onValueChange={(value) => write({ gitAccountWritesRepoConfig: value })}
/>
</SettingsSection>
{!projectSelected && selectedTargets.length === 1 ? (
<GitAccountsSection
settings={reference.settings}
disabled={disabled || accountManagers.size === 0}
onChange={write}
/>
) : null}
<SettingsSection title="Default branch">
<SettingsSwitchRow
icon="arrow.down.circle"
Expand Down
Loading
Loading