Skip to content

feat(pull-requests): resolve ssh config host aliases on git remotes - #6196

Closed
nyedle wants to merge 10 commits into
pingdotgg:mainfrom
nyedle:main
Closed

feat(pull-requests): resolve ssh config host aliases on git remotes#6196
nyedle wants to merge 10 commits into
pingdotgg:mainfrom
nyedle:main

Conversation

@nyedle

@nyedle nyedle commented Aug 11, 2026

Copy link
Copy Markdown

What Changed

Git remote URLs now go through ssh -G <host> wherever the server reads one,
so an ~/.ssh/config alias is understood as the host it points at.

useEnvironmentQuery no longer treats an interrupted read as a failed one.

Why

If you use a per account SSH key, your remote reads git@main:owner/repo. The
host parses as main, the provider resolves to unknown, and the repo never
lists its pull requests.

ssh -G does the resolving, so no config parser. Non ssh remotes, local paths
and a missing ssh are left alone.

Second fix, same page: refreshing re-arms five atoms at once and interrupts
whatever read is in flight. An interrupt arrives as a Failure, so the page
showed "the latest request failed" over rows that were fine. Retry always
worked because it refreshes one atom and interrupts nothing.

Note

Alias users will see the host switcher say GitHub instead of the alias name,
and two clones of one repo under different aliases now group as one.

UI Changes

None.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (n/a)
  • I included a video for animation/interaction changes (n/a)

Note

Medium Risk
Changes how git remotes are normalized for provider detection and repository identity, which can regroup clones and alter cached remote keys for SSH-alias users. Also introduces ssh -G process probes on remote reads.

Overview
Resolves ~/.ssh/config host aliases before provider detection and repository identity, so remotes like git@main:owner/repo are treated as their real host (e.g. GitHub) and PRs can load.

Adds canonicalizeSshRemoteUrl, which probes ssh -G (with a TTL hostname cache) and rewrites SCP/ssh:// remotes. Wired into GitManager remote reads, GitVcsDriver.listRemotes, and RepositoryIdentityResolver. Non-SSH URLs, local paths, and failed probes are left unchanged.

Also fixes useEnvironmentQuery so interrupt-only failures during refresh no longer surface as request errors.

Reviewed by Cursor Bugbot for commit 0fed439. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Resolve SSH config host aliases on git remote URLs before provider and repository identity resolution

  • Adds canonicalizeSshRemoteUrl which probes ssh -G to resolve SSH config aliases, bracket IPv6 hosts, and return a canonical hostname for any SSH or SCP remote URL.
  • GitVcsDriver.listRemotes, GitManager, and RepositoryIdentityResolver now canonicalize remote URLs before hosting provider detection, repository context resolution, and identity construction.
  • A SshHostnameCache with TTL is used to avoid redundant ssh -G invocations per host.
  • Non-SSH URLs and unresolvable hosts pass through unchanged.
  • Behavioral Change: hosting provider detection and repository identity now use the canonical hostname rather than the alias, which may change cached keys and resolved provider values for repos configured with SSH host aliases.

Macroscope summarized 0fed439.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bcac27ce-bb3c-41cb-b9dc-976dd6b54ae6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Aug 11, 2026

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the new sshRemoteUrl helper and its consumers (GitManager, GitVcsDriver, RepositoryIdentityResolver) against the Effect service conventions. Layer/dependency wiring follows the repo pattern (yield* ProcessRunner.ProcessRunner in make, Layer.provide(ProcessRunner.layer) on the exported layer), and the SshConfigProbe callback is a legitimate strategy since GitVcsDriver supplies a cwd-scoped VcsProcess variant. One finding: process-wide mutable cache state kept outside the Effect environment.

Posted via Macroscope — Effect Service Conventions

Comment thread apps/server/src/vcs/sshRemoteUrl.ts Outdated
Comment on lines +23 to +39
const HOSTNAME_TTL_MS = 5 * 60_000;
const effectiveHostnames = new Map<string, { readonly at: number; readonly hostname: string }>();

export const canonicalizeSshRemoteUrl = Effect.fnUntraced(function* (
remoteUrl: string,
probe: SshConfigProbe,
) {
const host = hostPattern(remoteUrl).exec(remoteUrl)?.[2];
if (host === undefined) return remoteUrl;

const now = Date.now();
const cached = effectiveHostnames.get(host);
const hostname =
(cached !== undefined && now - cached.at < HOSTNAME_TTL_MS ? cached.hostname : undefined) ??
/^hostname[ \t]+(\S+)/imu.exec(yield* probe(host))?.[1] ??
host;
effectiveHostnames.set(host, { at: now, hostname });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TTL cache is process-global mutable state living outside the Effect environment, and it reads time with Date.now() instead of the Effect clock. That has a few consequences worth avoiding: entries survive layer/runtime lifetimes and leak between tests (the "probes each host once" case depends on that global), TTL expiry can't be driven with TestClock, and because the key is only the host, a hostname resolved by GitVcsDriver's cwd-scoped ssh -G probe is reused for other cwds and for the ProcessRunner-backed probe, which may resolve differently.

Consider owning the memoization in the Effect environment instead — e.g. build it inside each make with Effect.cachedFunction/Cache.makeWith over the probe (which gives you clock-based TTL for free) and pass the memoized probe in, or expose a small ProcessRunner-backed service that owns the cache and keys it by probe context.

Posted via Macroscope — Effect Service Conventions

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the new sshRemoteUrl helper and its consumers (GitManager, GitVcsDriver, RepositoryIdentityResolver) against the Effect service conventions. Layer/dependency wiring follows the repo pattern (yield* ProcessRunner.ProcessRunner in make, Layer.provide(ProcessRunner.layer) on the exported layer), and the SshConfigProbe callback is a legitimate strategy since GitVcsDriver supplies a cwd-scoped VcsProcess variant. One finding: process-wide mutable cache state kept outside the Effect environment.

Posted via Macroscope — Effect Service Conventions

Comment thread apps/server/src/vcs/sshRemoteUrl.ts Outdated
Comment on lines +23 to +39
const HOSTNAME_TTL_MS = 5 * 60_000;
const effectiveHostnames = new Map<string, { readonly at: number; readonly hostname: string }>();

export const canonicalizeSshRemoteUrl = Effect.fnUntraced(function* (
remoteUrl: string,
probe: SshConfigProbe,
) {
const host = hostPattern(remoteUrl).exec(remoteUrl)?.[2];
if (host === undefined) return remoteUrl;

const now = Date.now();
const cached = effectiveHostnames.get(host);
const hostname =
(cached !== undefined && now - cached.at < HOSTNAME_TTL_MS ? cached.hostname : undefined) ??
/^hostname[ \t]+(\S+)/imu.exec(yield* probe(host))?.[1] ??
host;
effectiveHostnames.set(host, { at: now, hostname });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TTL cache is process-global mutable state living outside the Effect environment, and it reads time with Date.now() instead of the Effect clock. That has a few consequences worth avoiding: entries survive layer/runtime lifetimes and leak between tests (the "probes each host once" case depends on that global), TTL expiry can't be driven with TestClock, and because the key is only the host, a hostname resolved by GitVcsDriver's cwd-scoped ssh -G probe is reused for other cwds and for the ProcessRunner-backed probe, which may resolve differently.

Consider owning the memoization in the Effect environment instead — e.g. build it inside each make with Effect.cachedFunction/Cache.makeWith over the probe (which gives you clock-based TTL for free) and pass the memoized probe in, or expose a small ProcessRunner-backed service that owns the cache and keys it by probe context.

Posted via Macroscope — Effect Service Conventions

@macroscopeapp

macroscopeapp Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces new SSH config alias resolution functionality affecting multiple remote URL parsing paths. The architectural concerns about global mutable cache state and unresolved issues with certain SSH config patterns warrant human review.

You can customize Macroscope's approvability policy. Learn more.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 32708d8c4f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/server/src/vcs/sshRemoteUrl.ts Outdated
Comment on lines +41 to +43
return hostname === host
? remoteUrl
: remoteUrl.replace(hostPattern(remoteUrl), (_, prefix: string) => prefix + hostname);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle user-less SSH aliases when canonicalizing

When the remote relies on ~/.ssh/config for the user, e.g. Host main with HostName github.com and User git, Git remotes are commonly stored as main:owner/repo.git. This replacement preserves the empty prefix and returns github.com:owner/repo.git; the downstream source-control and repository parsers only recognize URL forms or git@... scp-style remotes, so these aliases still resolve as unknown/local-looking remotes and PR discovery remains broken for exactly that SSH-config workflow.

AGENTS.md reference: AGENTS.md:L19-L21

Useful? React with 👍 / 👎.

Comment on lines +17 to +18
export function environmentQueryError<A, E>(result: AsyncResult.AsyncResult<A, E>): string | null {
if (result._tag !== "Failure" || Cause.hasInterruptsOnly(result.cause)) return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Mirror interrupt filtering in mobile query state

This fixes interrupted AsyncResult reads only in the web copy of useEnvironmentQuery, while apps/mobile/src/state/query.ts still formats every Failure as an error. Any mobile screen that refreshes overlapping environment atoms can therefore continue showing the same spurious “environment request failed” state that this patch removes from web/desktop; move this logic to shared client code or update the mobile hook as well.

AGENTS.md reference: AGENTS.md:L65-L75

Useful? React with 👍 / 👎.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 585cf49. Configure here.

Comment thread apps/server/src/vcs/sshRemoteUrl.ts Outdated
Comment thread apps/server/src/vcs/sshRemoteUrl.ts Outdated
Comment thread apps/server/src/vcs/sshRemoteUrl.ts Outdated
@t3dotgg

t3dotgg commented Sep 4, 2026

Copy link
Copy Markdown
Member

Note

🤖 GPT-6 Astra (preview) responding on behalf of Theo

This was closed as part of an automated cleanup pass. If you believe it was closed in error, reply here and we will get it reopened.

Closing in favor of #7186. Its approach keeps the raw Git remote for SSH authentication and resolves a separate URL for provider detection, instead of rewriting stored remote locators. Review continues there. The direct GitManager lookup paths and SSH alias cases from this branch are recorded on that PR and still need coverage.

@t3dotgg t3dotgg closed this Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants