Skip to content

[Bug]: remotes using an SSH host alias get no forge (no PR sync, no clone) #17805

Open

Description

@dgregorybm

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

Build, CI, or release tooling

Steps to reproduce

The issue text below names no accounts or repos of ours. owner/repo and github-work are placeholders.


Remotes that use an SSH host alias (git@github-work:owner/repo.git) are classified as an unknown provider, so PRs never sync and threads never settle on merge

Setup

A common way to use two GitHub accounts on one machine is one SSH key per account, chosen by a host alias in ~/.ssh/[config](https://github.com/Users/dgregory/.ssh/config):

Host github-work
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_github_work
    IdentitiesOnly yes

and clones made through the alias: git clone git@github-work:owner/repo.git.
Git, ssh and gh all handle this (gh resolves the alias through ssh -G).

Cause (from reading the 0.0.45 bundle)

  1. pickPrimaryRemote takes upstream, then origin, from the fetch URLs in git remote -v.
  2. detectSourceControlProviderFromRemoteUrl classifies by the URL's host string. isGitHubHost accepts github.com or a host with a github DNS label. github-work is a single label, so the result is kind: "unknown"with baseUrl: https://github-work.
  3. refineUnknownProjectKinds then probes that baseUrl. https://github-work doesn't resolve, so the provider stays unknown, and nothing registered handles unknown.

Nothing consults ~/.ssh/[config](https://github.com/Users/<username>/.ssh/config). (selectGitHubBaseRepository's comment
already allows for remotes "under an SSH alias", so part of the code is aware of them.)

A dotted alias isn't a way out: github.work would be classified as "GitHub Self-Hosted" with base https://github.work, which gh isn't logged in to.

Suggested fix

For SSH remotes (scp-style user@host:path and ssh://), resolve the host alias before classifying:

ssh -G github-work | awk '$1 == "hostname" { print $2 }'    # -> github.com

Then classify and build baseUrl from the resolved hostname, keeping the original remote URL for git operations. ssh -G only reads the config. It opens no connection, so it's cheap and can be cached with the repository identity. Resolving the alias the same way in the clone flow, and cloning over SSH when the user gives an SSH alias, would cover the second symptom.

Expected behavior

Thread would auto-settle when the PR is merged.

Actual behavior

  • A thread's linked PR is never synced: projection_thread_pull_requests keeps snapshot_json NULL, so the thread doesn't settle when the PR merges.
  • boot-service.log repeats, on every sweep:
    • pull request sync skipped for github.com/owner/repo#N
    • thread branch pull request lookup failed ... No unknown source control provider is registered.
  • Related: "new project from GitHub" can't produce such a clone. Its gh lookup rejects the alias host, and it then clones over HTTPS, which fails on a headless server with no credential helper. Cloning by hand through the alias and adding the folder as an existing project works, apart from the PR sync above.

Impact

Major degradation or frequent failure

Version or commit

0.0.45

Environment

macOS 26.6.2 and ubuntu 26.04.1

Logs or stack traces

Screenshots, recordings, or supporting files

No response

Workaround

Add a second remote named upstream with the plain URL, which pickPrimaryRemote prefers, and keep origin on the alias for git:

git remote add upstream git@github.com:owner/repo.git
git config remote.upstream.skipFetchAll true
git config remote.upstream.pushurl no-push    # never push here

After the identity cache refreshed (about 9 minutes, no restart), the PRs synced with "state":"merged".

Activity

  1. added
    bugSomething is broken or behaving incorrectly.
    needs-triageIssue needs maintainer review and initial categorization.
    on Oct 10, 2026
  2. roodboi commented on Oct 14, 2026

    @roodboi

    I hit this too with two GitHub accounts on the same machine. I have a patch for existing checkouts that fixes the host lookup and the account used for PRs. I use multiple GitHub accounts with SSH aliases.

    In my case, git@github.com-work:example-org/demo.git gets treated as GitHub Self-Hosted at https://github.com-work. T3 Code then runs gh auth token --hostname github.com-work, even though the SSH alias points to github.com. That fails with GitHubNotSignedInError.

    The failure reaches listChangeRequests through ThreadSettlementServiceV2.pullRequestFor → lookupBranchPullRequest. The branch cannot find its existing PR, and the PR inbox shows a credential error.

    The patch uses ssh -G to read the alias's HostName before provider detection and API lookup. Git still uses the original alias for fetches and pushes.

    For accounts, it reads github.user from Git configuration. When gh supplies the token, a repo can select its account with git config --local github.user YOUR_LOGIN. T3 Code then uses gh auth token --hostname github.com --user YOUR_LOGIN. Saved tokens and environment tokens still take priority.

    The inbox also needs to use that account. It currently assumes one account per host, so Authored, Reviewing, and author:me can use the wrong login. The patch uses each repo's account for those views and keeps API batches separate when the accounts differ. PR details and code views use the repo's account too.

    Here is the same public checkout, alias, account, and search in both builds. Before, the inbox fails to load:

    Before: the PR inbox shows a GitHub credential error
    Image

    With the patch, it finds the existing PR:

    Image

    The PR description and changed files load too. These screenshots use public test data only.

    All 689 focused tests pass on macOS, Linux, and Windows. I also tested two real accounts in the macOS app, including Authored, author:me, PR details, and code.

    One new updated behavior I added: it reads global and conditional github.user values too. If the selected account is signed out, it reports that instead of switching accounts.

    This covers existing GitHub.com checkouts. It does not change the clone flow or add Enterprise alias support.

    Happy to put up a PR if this direction looks good.
    my patch is here: https://github.com/hack-dance/t3code-1/compare/main...fix/ssh-remote-github-account

  3. tsmith165 commented on Oct 22, 2026

    @tsmith165
    Contributor

    Note

    Claude Opus 5.5 in Claude Code, writing on behalf of @tsmith165. Measured on his Mac on 2026-10-22.

    Same setup here: two GitHub accounts, with remotes like git@github-personal:owner/repo.git. This is the server-side cost of the misdetection, from server.trace.ndjson.

    About 70 minutes spanning the 20261021 and 20261022 nightlies

    • GitHubCredentials.get failed 233 times.
    • ForgejoCli.listLogins ran tea 234 times.
    • PullRequestSyncReactor.syncGroup failed 61 of 61 times with "Change requests cannot be browsed for this project's host yet."
    • The server started 1,340 child processes in that window.

    28 minutes on 0.1.2-nightly.20261022.3526 alone

    • GitHubCredentials.get failed 16 of 16 times, taking up to 15.9 s each.
    • ForgejoCli.listLogins ran 34 times, and 17 of those runs failed.

    The repeated tea probes and failed credential lookups add load on every PR sync pass. Resolving the alias first (ssh -G <alias> and reading hostname) would classify these remotes as github.com. @roodboi's patch above looks like it would cover this case.

  4. dgregorybm commented on Oct 22, 2026

    @dgregorybm
    Author

    This is still happening with v0.1.0. Anyone know how to get the attention of @t3dotgg?

  5. roodboi commented on Oct 23, 2026

    @roodboi

    put up a PR for this: #23909. it resolves the alias with ssh -G so single-label ones like github-work work too, not just github.com-*, and picks the gh account per repo via github.user so the inbox stops mixing logins. overlaps a bit with #22246 on the alias part, kept it compatible with that.

  6. felipefa commented on Oct 23, 2026

    @felipefa

    Would love to get this issue fixed as well

  7. Zedyas commented on Oct 23, 2026

    @Zedyas

    Another effect of using the SSH alias as the repository host: the same repo on two machines (desktop plus an SSH environment) never groups into one project when each machine reaches GitHub through a different alias. The names below are placeholders.

    Setup

    Desktop (macOS), one key per GitHub account:

    Host gh-personal
      HostName github.com
      User git
      IdentityFile ~/.ssh/id_personal

    Clones use the alias: git@gh-personal:owner/repo.git.

    Server (Ubuntu, added in T3 Code as an SSH environment) acts as a separate machine-user account. Its repos were copied from the desktop, so they keep the desktop's remote URL, and the server's git rewrites that URL to its own alias:

    Host gh-bot
      HostName github.com
      User git
      IdentityFile ~/.ssh/id_bot
    git config --global --add url."git@gh-bot:".insteadOf "git@gh-personal:"
    git config --global --add url."git@gh-bot:".insteadOf "git@github.com:"

    Fetch and push work on both machines, and both remotes point at the same GitHub repository.

    Steps to reproduce

    1. Add the repo as a project on the desktop and on the server (same owner/repo).
    2. On the desktop, start a new thread in that project and open the "Run on" menu.

    Expected: the server is listed as a machine the project already runs on, and the sidebar shows one project.
    Actual: only the desktop is listed, and the server appears under "Add to another machine", although the server already has the project registered (t3 project add there reports "An active project already exists").

    Cause

    • apps/server/src/project/RepositoryIdentityResolver.ts builds the identity from git remote -v. That output already has url.insteadOf applied, so the server reports git@gh-bot:owner/repo.git while the desktop reports git@gh-personal:owner/repo.git.
    • normalizeGitRemoteUrl (packages/shared/src/git.ts) keeps the SCP-style host as written, giving canonicalKey values of gh-personal/owner/repo on the desktop and gh-bot/owner/repo on the server.
    • Grouping keys on that value (deriveRepositoryScopedKey in packages/client-runtime/src/state/projectGrouping.ts, through repositoryGroupingKeyOf), so the two environments' projects never meet.

    Either difference is enough on its own: two different aliases, or the same alias plus a URL rewrite on one side.

    Relation to #23909

    #23909 looks like it fixes this case too: its GitHub refineRepositoryIdentity rewrites canonicalKey (and origin.canonicalKey) to the ssh -G HostName, so both machines would produce github.com/owner/repo. Two requests if it goes ahead:

    • A regression test for two environments whose aliases differ but resolve to the same HostName, asserting the same grouping key, so cross-environment grouping stays covered.
    • The refinement only runs once the provider is recognised as GitHub. Remotes on other forges, or ones the GitHub check doesn't recognise, would still split. Resolving the alias's HostName before normalizeGitRemoteUrl for every provider would cover those too.

    Workaround

    Make git remote -v print the same URL on both machines. On the server, drop the url.insteadOf rewrites and give the server's Host block the desktop's alias as extra names, with the same key:

    Host gh-bot gh-personal github.com
      HostName github.com
      User git
      IdentityFile ~/.ssh/id_bot
      IdentitiesOnly yes

    Both machines then normalise to gh-personal/owner/repo, and git on the server still uses its own key. The resolver caches a positive identity for 15 minutes, so restart the server (or wait) before checking.

    Version

    0.1.3-nightly.20261023.3579 on both machines (desktop app on macOS 26.6.2; server on Ubuntu 26.04.1 LTS, reached over SSH).

  8. roodboi commented on Oct 23, 2026

    @roodboi

    #23909 would cover this for github since refineRepositoryIdentity rewrites the canonicalKey to the ssh -G HostName before the identity gets cached, so two envs with different github aliases end up with the same grouping key. added a regression test for that (two aliases, same host, same key) and it groups fine. github.com only though, so GHE / gitlab / self-hosted aliases still split.

    doing the alias resolution at every provider makes sense too i think, but prob as a follow up? Like move into the shared resolver instead of living in each provider, and the other normalizeGitRemoteUrl callers (GitManager, AgentSessionScanner) would need it too or theyd still disagree on remotes. prob a bigger change than this PR/routing.

  9. LeonHeidelbach commented on Nov 2, 2026

    @LeonHeidelbach

    Same problem with a self-hosted GitLab behind an SSH alias (nightly 0.1.7, November 2026). The remote is git@gitlab-work:group/project.git, and ~/.ssh/config maps gitlab-work to HostName gitlab.example.com with a non-standard Port. glab is logged in to gitlab.example.com (glab auth status lists it, with ssh_host set to the alias).

    • What T3 does: the alias is classified as unknown. The GitLab refinement (refineUnknownGitLabRemote) compares gitlab-work against the hosts from glab auth status, finds no match, and T3 falls back to GitHub credentials (GitHubNotSignedInError: No GitHub credential for gitlab-work).
    • Result: linked MRs on gitlab.example.com stay state: null with "Change requests cannot be browsed for this project's host yet".
    • Workaround: add a Host gitlab.example.com entry with the alias's settings and point the remote at the real hostname. After that, sync works immediately.

    Resolving the alias with ssh -G before classifying, as suggested above, would fix this for GitLab too. The closed #23909 only handled github.com.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.needs-triageIssue needs maintainer review and initial categorization.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions