# Files changed · PR \#23909 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/23909/diffs)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

[fix\(source-control\): resolve SSH aliases and repository GitHub accounts](https://git.cafe/pingdotgg/t3code/pull/23909.md)

Version: 11

Head commit: 3d94852d1950f8127896f6ca81ac0143b4674196

Base commit: 33f1fe5ebe53fdd8d18b6a5d63dfd29c336ba5a9

Comparison base: 33f1fe5ebe53fdd8d18b6a5d63dfd29c336ba5a9

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## apps/mobile/src/features/pull-requests/pullRequestList.test.ts

Status: modified; +47; −0

```diff
@@ -349,6 +349,53 @@
   });
 });
 
+it("uses row accounts for mobile Authored and author:me filters", () => {
+  const rows = [
+    entry(1, { viewer: "work-user", author: { login: "work-user", name: null, avatarUrl: null } }),
+    entry(2, {
+      viewer: "personal-user",
+      author: { login: "personal-user", name: null, avatarUrl: null },
+    }),
+    entry(3, {
+      viewer: "personal-user",
+      author: { login: "work-user", name: null, avatarUrl: null },
+    }),
+  ];
+  const viewers = { "github.com": "work-user" };
+  expect(
+    filterPullRequestList(
+      rows,
+      { ...DEFAULT_PR_LIST_CONTROLS, involvement: "authored" },
+      viewers,
+      "",
+      new Set(),
+    ).map((row) => row.number),
+  ).toEqual([2, 1]);
+  expect(
+    filterPullRequestList(
+      rows,
+      { ...DEFAULT_PR_LIST_CONTROLS, filters: { author: "me" } },
+      viewers,
+      "",
+      new Set(),
+    ).map((row) => row.number),
+  ).toEqual([2, 1]);
+  expect(
+    filterPullRequestList(
+      [entry(4, { author: { login: "work-user", name: null, avatarUrl: null } })],
+      { ...DEFAULT_PR_LIST_CONTROLS, involvement: "authored" },
+      viewers,
+      "",
+      new Set(),
+    ).map((row) => row.number),
+  ).toEqual([4]);
+});
+
+it("rerenders a mobile inbox row when its selected account changes", () => {
+  const row = { ...entry(1, { viewer: "work-user" }), environmentId: EnvironmentId.make("test") };
+  expect(pullRequestListRowsAreEqual(row, { ...row, viewer: "personal-user" })).toBe(false);
+});
+
 describe("pullRequestLabelColors", () => {
   it("keeps the first usable color each label name carries", () => {
     expect(
```

## apps/mobile/src/features/pull-requests/pullRequestList.ts

Status: modified; +2; −1

```diff
@@ -67,6 +67,7 @@
       previous.deletions === next.deletions &&
       previous.checksState === next.checksState &&
       previous.reviewDecision === next.reviewDecision &&
+      previous.viewer === next.viewer &&
       previous.viewerReviewRequested === next.viewerReviewRequested &&
       previous.author?.login === next.author?.login &&
       previous.author?.avatarUrl === next.author?.avatarUrl &&
@@ -260,7 +261,7 @@
     if (controls.projectEnvironmentId && entry.environmentId !== controls.projectEnvironmentId)
       return false;
     const viewerKey = entry.environmentId ? `${entry.environmentId} ${entry.host}` : entry.host;
+    const viewer = entry.viewer ?? viewers[viewerKey];
-    const viewer = viewers[viewerKey];
     if (controls.involvement === "reviewing" && !entry.viewerReviewRequested) return false;
     if (
       controls.involvement === "authored" &&
```

## apps/server/src/assets/AssetAccess.test.ts

Status: modified; +11; −8

```diff
@@ -196,14 +196,17 @@
       Effect.provide(
         Layer.mock(GitHubCredentials.GitHubCredentials)({
           get: (host) =>
+            Effect.gen(function* () {
+              expect(yield* GitHubCredentials.RepositoryDirectory).toBe("/repo");
+              return yield* ++lookups === 1
+                ? Effect.fail(new GitHubCredentials.GitHubNotSignedInError({ host }))
+                : Effect.succeed({
+                    host,
+                    token: Redacted.make("signed-in"),
+                    source: "gh" as const,
+                    fingerprint: "fingerprint",
+                  });
-            ++lookups === 1
+            }),
-              ? Effect.fail(new GitHubCredentials.GitHubNotSignedInError({ host }))
-              : Effect.succeed({
-                  host,
-                  token: Redacted.make("signed-in"),
-                  source: "gh" as const,
-                  fingerprint: "fingerprint",
-                }),
         }),
       ),
       Effect.provideService(
```

## apps/server/src/assets/GitHubMediaFetch.ts

Status: modified; +3; −1

```diff
@@ -92,7 +92,9 @@
   requestHeaders: Record<string, string | undefined>,
 ) {
   // Both media hosts are served by github.com's account, which is the host `gh` stores it under.
-  const token = yield* githubToken;
+  const token = yield* githubToken.pipe(
+    Effect.provideService(GitHubCredentials.RepositoryDirectory, asset.cwd),
+  );
   const forwarded: Record<string, string> = {};
   for (const name of FORWARDED_REQUEST_HEADERS) {
     const value = requestHeaders[name];
```

## apps/server/src/pullRequest/PullRequestService.test.ts

Status: modified; +744; −0

```diff
@@ -229,10 +229,37 @@
   };
 }
 
+function inboxProvider(
+  accounts: Readonly<Record<string, string>>,
+  overrides: Partial<PullRequestProviderApi> = {},
+) {
+  return fakeProvider(SourceControlProviderKind.make("github"), {
+    withVerifiedCredential: (input, use) => {
+      assert.isFalse(input.allowReserve);
+      const viewer = accounts[input.cwd];
+      if (viewer === undefined)
+        return Effect.fail(
+          new PullRequestProviderError({
+            provider: SourceControlProviderKind.make("github"),
+            operation: "getViewer",
+            reason: "unauthenticated",
+            detail: "The selected test account is signed out.",
+          }),
+        );
+      return use({ accountId: viewer, viewer, credentialFingerprint: viewer }).pipe(
+        Effect.provideService(SourceControlRateLimit.CredentialScope, viewer),
+      );
+    },
+    getViewer: () => Effect.die("Account-aware inbox reads must not use a host-wide viewer"),
+    ...overrides,
+  });
+}
+
 function makeService(input: {
   readonly settings?: ServerSettings;
   readonly projects: ReadonlyArray<OrchestrationProjectShell>;
   readonly providers: ReadonlyArray<PullRequestProviderApi>;
+  readonly sshHostname?: string;
   readonly resolveHandle?: SourceControlProviderRegistry.SourceControlProviderRegistry["Service"]["resolveHandle"];
   readonly resolveRepositoryIdentity?: RepositoryIdentityResolver.RepositoryIdentityResolver["Service"]["resolve"];
 }) {
@@ -264,6 +291,18 @@
         Layer.mock(RepositoryIdentityResolver.RepositoryIdentityResolver)({
           resolve: input.resolveRepositoryIdentity ?? (() => Effect.succeed(null)),
         }),
+        TestSourceControlHost.layer({
+          process: {
+            run: () =>
+              Effect.succeed({
+                stdout: `hostname ${input.sshHostname ?? "unresolved"}\n`,
+                stderr: "",
+                exitCode: ChildProcessSpawner.ExitCode(0),
+                stdoutTruncated: false,
+                stderrTruncated: false,
+              }),
+          },
+        }),
         SourceControlRateLimit.layer,
         PullRequestFilesViewed.layer.pipe(Layer.provide(SqlitePersistence.layerMemory)),
         Layer.effect(PullRequestReadCache.PullRequestReadCache, PullRequestReadCache.make).pipe(
@@ -3195,6 +3234,59 @@
   }),
 );
 
+it.effect("keeps account-bound resume positions across filtered slices and continuations", () =>
+  Effect.gen(function* () {
+    const accounts = { "/work": "work-user" };
+    const resumed: Array<string | undefined> = [];
+    const service = yield* makeService({
+      projects: [
+        project({ id: "w", title: "Work", workspaceRoot: "/work", repository: "acme/web" }),
+      ],
+      providers: [
+        inboxProvider(accounts, {
+          listChangeRequests: (input) =>
+            Effect.gen(function* () {
+              assert.strictEqual(yield* SourceControlRateLimit.CredentialScope, "work-user");
+              resumed.push(input.cursor?.resumeAfter);
+              const delivered = input.cursor?.delivered ?? 0;
+              return {
+                items: [
+                  {
+                    ...changeRequest(delivered + 1, "2026-07-02T00:00:00Z"),
+                    labels: delivered === 0 ? [] : [{ name: "bug", color: null }],
+                  },
+                ],
+                truncated: true,
+                continues: true,
+                resumeAfter: `2026-07-01T00:00:00.000Z~${delivered + 1}`,
+              };
+            }),
+        }),
+      ],
+    });
+    const input = { state: "open" as const, limit: 1, filters: { labels: [["bug"]] } };
+    const first = yield* service.list(input);
+    assert.deepStrictEqual(
+      first.entries.map((entry) => entry.number),
+      [2],
+    );
+    const next = yield* service.list({ ...input, cursors: first.nextCursors });
+    assert.deepStrictEqual(
+      next.entries.map((entry) => entry.number),
+      [3],
+    );
+    assert.deepStrictEqual(resumed, [
+      undefined,
+      "2026-07-01T00:00:00.000Z~1",
+      "2026-07-01T00:00:00.000Z~2",
+    ]);
+    accounts["/work"] = "personal-user";
+    const error = yield* service.list({ ...input, cursors: next.nextCursors }).pipe(Effect.flip);
+    assert.strictEqual(error._tag, "PullRequestOperationError");
+    assert.strictEqual(resumed.length, 3);
+  }),
+);
+
 it.effect("hands a host's own resume position back on the next read", () =>
   Effect.gen(function* () {
     const resumed: Array<string | undefined> = [];
@@ -4099,3 +4191,655 @@
     assert.deepStrictEqual(read, ["src/old.ts->src/new.ts", "gone.md->gone.md"]);
   }),
 );
+
+it.effect(
+  "reads an aliased project's PR with its checkout when another GitHub project exists",
+  () =>
+    Effect.gen(function* () {
+      const reads: Array<{ cwd: string; host: string }> = [];
+      const service = yield* makeService({
+        projects: [
+          project({
+            id: "work",
+            title: "work",
+            workspaceRoot: "/work repo",
+            repository: "acme/web",
+            host: "github.com-work",
+            remoteUrl: "git@github.com-work:acme/web.git",
+          }),
+          project({
+            id: "personal",
+            title: "personal",
+            workspaceRoot: "/personal repo",
+            repository: "fake-user/demo",
+          }),
+        ],
+        providers: [
+          fakeProvider(SourceControlProviderKind.make("github"), {
+            getChangeRequest: (input) => {
+              reads.push({ cwd: input.cwd, host: input.host });
+              return Effect.succeed(hostedChangeRequest("Work PR"));
+            },
+          }),
+        ],
+        resolveHandle: ({ context }) =>
+          Effect.succeed({
+            provider: undefined as never,
+            context: {
+              ...context!,
+              provider: {
+                kind: SourceControlProviderKind.make("github"),
+                name: "GitHub",
+                baseUrl: "https://github.com",
+              },
+            },
+          }),
+      });
+      const detail = yield* service.detail({
+        projectId: "work" as ProjectId,
+        repository: "acme/web",
+        host: "github.com",
+        number: 7,
+      });
+      assert.strictEqual(detail.workspaceRoot, "/work repo");
+      assert.deepStrictEqual(reads, [{ cwd: "/work repo", host: "github.com" }]);
+    }),
+);
+
+it.effect("uses each checkout's account for inbox searches and author filters", () =>
+  Effect.gen(function* () {
+    const searches: Array<{ scope: string; viewer: string; repositories: ReadonlyArray<string> }> =
+      [];
+    const service = yield* makeService({
+      projects: [
+        project({ id: "w", title: "Work", workspaceRoot: "/work", repository: "acme/web" }),
+        project({ id: "w2", title: "Other work", workspaceRoot: "/other", repository: "acme/api" }),
+        project({
+          id: "p",
+          title: "Personal",
+          workspaceRoot: "/personal",
+          repository: "fake-user/demo",
+        }),
+      ],
+      providers: [
+        inboxProvider(
+          { "/work": "work-user", "/other": "work-user", "/personal": "personal-user" },
+          {
+            listChangeRequestsAcross: (input) =>
+              Effect.gen(function* () {
+                const scope = yield* SourceControlRateLimit.CredentialScope;
+                searches.push({ scope, viewer: input.viewer, repositories: input.repositories });
+                assert.strictEqual(scope, input.viewer);
+                return {
+                  items: input.repositories.flatMap((repository) => [
+                    {
+                      ...changeRequest(1, "2026-07-02T00:00:00Z"),
+                      repository,
+                      author: { login: scope, name: null, avatarUrl: null },
+                    },
+                    { ...changeRequest(2, "2026-07-02T00:00:00Z"), repository },
+                  ]),
+                  truncated: false,
+                };
+              }),
+          },
+        ),
+      ],
+    });
+    const result = yield* service.list({
+      state: "open",
+      involvement: "authored",
+      filters: { author: "me" },
+    });
+    assert.strictEqual(searches.length, 2);
+    assert.deepStrictEqual(searches.map((search) => search.repositories.length).toSorted(), [1, 2]);
+    assert.deepStrictEqual(
+      result.entries.map((entry) => [entry.projectId, entry.viewer]).toSorted(),
+      [
+        ["p", "personal-user"],
+        ["w", "work-user"],
+        ["w2", "work-user"],
+      ],
+    );
+    assert.strictEqual(result.providers.length, 1);
+    assert.isTrue(result.providers[0]!.configured);
+  }),
+);
+
+it.effect("keeps a healthy inbox account available when another login is missing", () =>
+  Effect.gen(function* () {
+    const service = yield* makeService({
+      projects: [
+        project({ id: "w", title: "Work", workspaceRoot: "/work", repository: "acme/web" }),
+        project({
+          id: "p",
+          title: "Personal",
+          workspaceRoot: "/personal",
+          repository: "fake-user/demo",
+        }),
+      ],
+      providers: [
+        inboxProvider(
+          { "/personal": "personal-user" },
+          {
+            listChangeRequests: () =>
+              Effect.succeed({
+                items: [changeRequest(1, "2026-07-02T00:00:00Z")],
+                truncated: false,
+                continues: true,
+              }),
+          },
+        ),
+      ],
+    });
+    const result = yield* service.list({ state: "open" });
+    assert.deepStrictEqual(
+      result.entries.map((entry) => entry.projectId),
+      ["p"],
+    );
+    assert.deepStrictEqual(
+      result.errors.map((error) => error.projectId),
+      ["w"],
+    );
+    assert.isTrue(result.providers[0]!.configured);
+  }),
+);
+
+it.effect("pauses only the account that reached its inbox rate limit", () =>
+  Effect.gen(function* () {
+    const reads: string[] = [];
+    const service = yield* makeService({
+      projects: [
+        project({ id: "w", title: "Work", workspaceRoot: "/work", repository: "acme/web" }),
+        project({
+          id: "p",
+          title: "Personal",
+          workspaceRoot: "/personal",
+          repository: "fake-user/demo",
+        }),
+      ],
+      providers: [
+        inboxProvider(
+          { "/work": "work-user", "/personal": "personal-user" },
+          {
+            listChangeRequests: () =>
+              Effect.gen(function* () {
+                const scope = yield* SourceControlRateLimit.CredentialScope;
+                reads.push(scope);
+                if (scope === "work-user")
+                  return yield* new PullRequestProviderError({
+                    provider: SourceControlProviderKind.make("github"),
+                    operation: "list",
+                    reason: "rate-limited",
+                    detail: "Test account is rate limited.",
+                  });
+                return {
+                  items: [changeRequest(1, "2026-07-02T00:00:00Z")],
+                  truncated: false,
+                  continues: true,
+                };
+              }),
+          },
+        ),
+      ],
+    });
+    yield* service.list({ state: "open" });
+    const result = yield* service.list({ state: "closed" });
+    assert.strictEqual(reads.filter((scope) => scope === "work-user").length, 1);
+    assert.strictEqual(reads.filter((scope) => scope === "personal-user").length, 2);
+    assert.deepStrictEqual(
+      result.entries.map((entry) => entry.projectId),
+      ["p"],
+    );
+  }),
+);
+
+it.effect("keeps repository pagination stable and shares matching-account worktree reads", () =>
+  Effect.gen(function* () {
+    const reads: Array<{ viewer: string; cursor: unknown }> = [];
+    const service = yield* makeService({
+      projects: [
+        project({ id: "w", title: "Work", workspaceRoot: "/work", repository: "acme/web" }),
+        project({
+          id: "w2",
+          title: "Worktree",
+          workspaceRoot: "/worktree",
+          repository: "acme/web",
+        }),
+        project({
+          id: "p",
+          title: "Personal",
+          workspaceRoot: "/personal",
+          repository: "fake-user/demo",
+        }),
+      ],
+      providers: [
+        inboxProvider(
+          { "/work": "work-user", "/worktree": "work-user", "/personal": "personal-user" },
+          {
+            listChangeRequests: (input) =>
+              Effect.gen(function* () {
+                assert.strictEqual(yield* SourceControlRateLimit.CredentialScope, input.viewer);
+                reads.push({ viewer: input.viewer, cursor: input.cursor });
+                return {
+                  items: [
+                    changeRequest(input.viewer === "work-user" ? 1 : 2, "2026-07-02T00:00:00Z"),
+                  ],
+                  truncated: true,
+                  continues: true,
+                };
+              }),
+          },
+        ),
+      ],
+    });
+    const first = yield* service.list({ state: "open", limit: 1 });
+    assert.strictEqual(reads.length, 2);
+    assert.strictEqual(Object.keys(first.nextCursors).length, 2);
+    yield* service.list({
+      state: "open",
+      limit: 1,
+      cursors: {
+        "github.com acme/web": first.nextCursors["github.com acme/web"]!,
+      },
+    });
+    assert.deepStrictEqual(
+      reads.map((read) => read.viewer),
+      ["work-user", "personal-user", "work-user"],
+    );
+    assert.isDefined(reads[2]!.cursor);
+  }),
+);
+
+it.effect(
+  "reads inbox diff counts under each account and retains worktree project references",
+  () =>
+    Effect.gen(function* () {
+      const scopes: string[] = [];
+      const service = yield* makeService({
+        projects: [
+          project({ id: "w", title: "Work", workspaceRoot: "/work", repository: "acme/web" }),
+          project({
+            id: "w2",
+            title: "Worktree",
+            workspaceRoot: "/worktree",
+            repository: "acme/web",
+          }),
+          project({
+            id: "p",
+            title: "Personal",
+            workspaceRoot: "/personal",
+            repository: "acme/web",
+          }),
+        ],
+        providers: [
+          inboxProvider(
+            { "/work": "work-user", "/worktree": "work-user", "/personal": "personal-user" },
+            {
+              listChangeRequestStats: (input) =>
+                Effect.gen(function* () {
+                  const scope = yield* SourceControlRateLimit.CredentialScope;
+                  scopes.push(scope);
+                  return [
+                    ...new Map(input.changeRequests.map((ref) => [ref.number, ref])).values(),
+                  ].map((ref) => ({
+                    ...ref,
+                    additions: scope === "work-user" ? 10 : 20,
+                    deletions: 0,
+                  }));
+                }),
+            },
+          ),
+        ],
+      });
+      const result = yield* service.listStats({
+        refs: ["w", "w2", "p"].map((id) => ({
+          projectId: id as ProjectId,
+          repository: "acme/web",
+          number: 1,
+        })),
+      });
+      assert.deepStrictEqual(scopes.toSorted(), ["personal-user", "work-user"]);
+      assert.deepStrictEqual(
+        result.stats.map((stat) => [stat.projectId, stat.additions]).toSorted(),
+        [
+          ["p", 20],
+          ["w", 10],
+          ["w2", 10],
+        ],
+      );
+    }),
+);
+
+it.effect.each([false, true])(
+  "lists a duplicate repository once using its first readable checkout (signed out: %s)",
+  (signedOut) =>
+    Effect.gen(function* () {
+      const captures: string[] = [];
+      const provider = inboxProvider(
+        signedOut
+          ? { "/personal": "personal-user" }
+          : { "/work": "work-user", "/personal": "personal-user" },
+        {
+          listChangeRequests: () =>
+            Effect.succeed({
+              items: [changeRequest(7, "2026-07-02T00:00:00Z")],
+              truncated: true,
+              continues: true,
+            }),
+        },
+      );
+      const service = yield* makeService({
+        projects: [
+          project({ id: "w", title: "Work", workspaceRoot: "/work", repository: "acme/web" }),
+          project({
+            id: "p",
+            title: "Personal",
+            workspaceRoot: "/personal",
+            repository: "acme/web",
+          }),
+        ],
+        providers: [
+          {
+            ...provider,
+            withVerifiedCredential: (input, use) => {
+              captures.push(input.cwd);
+              return provider.withVerifiedCredential!(input, use);
+            },
+          },
+        ],
+      });
+      const result = yield* service.list({ state: "open", limit: 1 });
+      assert.strictEqual(result.entries.length, 1);
+      assert.strictEqual(result.entries[0]!.viewer, signedOut ? "personal-user" : "work-user");
+      assert.deepStrictEqual(Object.keys(result.nextCursors), ["github.com acme/web"]);
+      assert.deepStrictEqual(result.errors, []);
+      if (!signedOut) assert.isFalse(captures.includes("/personal"));
+      const selected = yield* service.list({ state: "open", projectId: "p" as ProjectId });
+      assert.strictEqual(selected.entries[0]!.projectId, "p");
+      assert.strictEqual(selected.entries[0]!.viewer, "personal-user");
+    }),
+);
+
+it.effect("does not lose one account diff counts when another account read fails", () =>
+  Effect.gen(function* () {
+    const service = yield* makeService({
+      projects: [
+        project({ id: "w", title: "Work", workspaceRoot: "/work", repository: "acme/web" }),
+        project({
+          id: "p",
+          title: "Personal",
+          workspaceRoot: "/personal",
+          repository: "fake-user/demo",
+        }),
+      ],
+      providers: [
+        inboxProvider(
+          { "/work": "work-user", "/personal": "personal-user" },
+          {
+            listChangeRequestStats: (input) =>
+              Effect.gen(function* () {
+                const scope = yield* SourceControlRateLimit.CredentialScope;
+                if (scope === "work-user")
+                  return yield* new PullRequestProviderError({
+                    provider: SourceControlProviderKind.make("github"),
+                    operation: "stats",
+                    reason: "not-found",
+                    detail: "Fake private repository is unavailable.",
+                  });
+                return input.changeRequests.map((ref) => ({ ...ref, additions: 20, deletions: 1 }));
+              }),
+          },
+        ),
+      ],
+    });
+    const result = yield* service.listStats({
+      refs: [
+        { projectId: "w" as ProjectId, repository: "acme/web", number: 1 },
+        { projectId: "p" as ProjectId, repository: "fake-user/demo", number: 1 },
+      ],
+    });
+    assert.deepStrictEqual(
+      result.stats.map((stat) => [stat.projectId, stat.additions]),
+      [["p", 20]],
+    );
+  }),
+);
+
+it.effect("marks review requests with each repository selected account", () =>
+  Effect.gen(function* () {
+    const service = yield* makeService({
+      projects: [
+        project({ id: "w", title: "Work", workspaceRoot: "/work", repository: "acme/web" }),
+        project({
+          id: "p",
+          title: "Personal",
+          workspaceRoot: "/personal",
+          repository: "fake-user/demo",
+        }),
+      ],
+      providers: [
+        inboxProvider(
+          { "/work": "work-user", "/personal": "personal-user" },
+          {
+            listChangeRequestsAcross: (input) =>
+              Effect.gen(function* () {
+                const scope = yield* SourceControlRateLimit.CredentialScope;
+                assert.strictEqual(scope, input.viewer);
+                return {
+                  items: input.repositories.map((repository) => ({
+                    ...changeRequest(7, "2026-07-02T00:00:00Z"),
+                    repository,
+                    reviewRequestLogins: [scope],
+                  })),
+                  truncated: false,
+                };
+              }),
+          },
+        ),
+      ],
+    });
+    const result = yield* service.list({ state: "open", involvement: "reviewing" });
+    assert.deepStrictEqual(
+      result.entries
+        .map((row) => [row.projectId, row.viewer, row.viewerReviewRequested])
+        .toSorted(),
+      [
+        ["p", "personal-user", true],
+        ["w", "work-user", true],
+      ],
+    );
+  }),
+);
+
+it.effect.each(["gitlab", "forgejo", "bitbucket", "azure-devops"] as const)(
+  "keeps failed %s viewer lookups shared across duplicate checkouts",
+  (kind) =>
+    Effect.gen(function* () {
+      let lookups = 0;
+      const service = yield* makeService({
+        projects: Array.from({ length: 10 }, (_, i) =>
+          project({
+            id: "p" + i,
+            title: "Checkout",
+            workspaceRoot: "/repo" + i,
+            repository: "acme/web",
+            provider: kind,
+          }),
+        ),
+        providers: [
+          fakeProvider(SourceControlProviderKind.make(kind), {
+            getViewer: () => {
+              lookups += 1;
+              return Effect.fail(
+                new PullRequestProviderError({
+                  provider: SourceControlProviderKind.make(kind),
+                  operation: "viewer",
+                  reason: "unauthenticated",
+                  detail: "Test host is signed out.",
+                }),
+              );
+            },
+          }),
+        ],
+      });
+      yield* Effect.flip(service.list({ state: "open" }));
+      assert.strictEqual(lookups, 10);
+    }),
+);
+
+it.effect("looks up a failed non-GitHub host once for all of its repositories", () =>
+  Effect.gen(function* () {
+    let lookups = 0;
+    const kind = SourceControlProviderKind.make("gitlab");
+    const service = yield* makeService({
+      projects: Array.from({ length: 20 }, (_, i) =>
+        project({
+          id: "p" + i,
+          title: "Repository",
+          workspaceRoot: "/repo" + i,
+          repository: "acme/repo" + i,
+          provider: kind,
+        }),
+      ),
+      providers: [
+        fakeProvider(kind, {
+          getViewer: () => {
+            lookups += 1;
+            return Effect.fail(
+              new PullRequestProviderError({
+                provider: kind,
+                operation: "viewer",
+                reason: "unauthenticated",
+                detail: "Test host is signed out.",
+              }),
+            );
+          },
+        }),
+      ],
+    });
+    yield* Effect.flip(service.list({ state: "open" }));
+    assert.strictEqual(lookups, 20);
+  }),
+);
+
+it.effect("refuses continuation under another account while accepting legacy cursors", () =>
+  Effect.gen(function* () {
+    const accounts: Record<string, string> = { "/work": "work-user", "/personal": "personal-user" };
+    let reads = 0;
+    const service = yield* makeService({
+      projects: [
+        project({ id: "w", title: "Work", workspaceRoot: "/work", repository: "acme/web" }),
+        project({ id: "p", title: "Personal", workspaceRoot: "/personal", repository: "acme/web" }),
+      ],
+      providers: [
+        inboxProvider(accounts, {
+          listChangeRequests: () => {
+            reads += 1;
+            return Effect.succeed({
+              items: [changeRequest(7, "2026-07-02T00:00:00Z")],
+              truncated: true,
+              continues: true,
+            });
+          },
+        }),
+      ],
+    });
+    const first = yield* service.list({ state: "open", limit: 1 });
+    assert.isTrue(first.nextCursors["github.com acme/web"]!.endsWith("|work-user"));
+    delete accounts["/work"];
+    const error = yield* Effect.flip(
+      service.list({ state: "open", limit: 1, cursors: first.nextCursors }),
+    );
+    assert.strictEqual(error._tag, "PullRequestOperationError");
+    if (error._tag === "PullRequestOperationError")
+      assert.include(error.detail, "Refresh the inbox");
+    assert.strictEqual(reads, 1);
+    yield* service.list({
+      state: "open",
+      limit: 1,
+      cursors: { "github.com acme/web": "2026-07-02T00:00:00Z|1|7" },
+    });
+    assert.strictEqual(reads, 2);
+  }),
+);
+
+it.effect("reports the first checkout when all duplicate repository accounts fail", () =>
+  Effect.gen(function* () {
+    const service = yield* makeService({
+      projects: [
+        project({ id: "w", title: "Work", workspaceRoot: "/work", repository: "acme/web" }),
+        project({ id: "p", title: "Personal", workspaceRoot: "/personal", repository: "acme/web" }),
+        project({
+          id: "h",
+          title: "Healthy",
+          workspaceRoot: "/healthy",
+          repository: "fake-user/demo",
+        }),
+      ],
+      providers: [
+        inboxProvider(
+          { "/healthy": "healthy-user" },
+          {
+            listChangeRequests: () =>
+              Effect.succeed({ items: [], truncated: false, continues: true }),
+          },
+        ),
+      ],
+    });
+    const result = yield* service.list({ state: "open" });
+    assert.deepStrictEqual(
+      result.errors.map((error) => error.projectId),
+      ["w"],
+    );
+  }),
+);
+
+it.effect("includes an unknown SSH alias in a GitHub host-filtered inbox", () =>
+  Effect.gen(function* () {
+    const service = yield* makeService({
+      sshHostname: "github.com",
+      projects: [
+        project({
+          id: "alias",
+          title: "Alias",
+          workspaceRoot: "/repo",
+          repository: "acme/web",
+          provider: "unknown",
+          host: "github-work",
+          remoteUrl: "git@github-work:acme/web.git",
+        }),
+      ],
+      providers: [
+        fakeProvider(SourceControlProviderKind.make("github"), {
+          listChangeRequests: () =>
+            Effect.succeed({
+              items: [changeRequest(42, "2026-07-05T00:00:00Z")],
+              truncated: false,
+              continues: false,
+            }),
+        }),
+      ],
+      resolveHandle: ({ context }) =>
+        Effect.succeed({
+          provider: undefined as never,
+          context: {
+            ...context!,
+            provider:
+              context?.requestedHost === undefined
+                ? {
+                    kind: SourceControlProviderKind.make("github"),
+                    name: "GitHub",
+                    baseUrl: "https://github.com",
+                  }
+                : context.provider,
+          },
+        }),
+    });
+    const result = yield* service.list({ state: "open", host: "github.com" });
+    assert.deepStrictEqual(
+      result.entries.map(({ host, number }) => [host, number]),
+      [["github.com", 42]],
+    );
+  }),
+);
```

## apps/server/src/pullRequest/PullRequestService.ts

Status: modified; +386; −145

```diff
@@ -5,6 +5,8 @@
 } from "@t3tools/shared/sourceControl";
 import { normalizeGitRemoteUrl } from "@t3tools/shared/git";
 import { parseDiffFilePaths } from "@t3tools/source-control-core/server/diffFileRevisions";
+import * as SourceControlHost from "@t3tools/source-control-core/server/SourceControlHost";
+import { resolveSshRemoteUrl } from "@t3tools/source-control-core/server/sshRemote";
 import * as Cache from "effect/Cache";
 import * as Cause from "effect/Cause";
 import * as Clock from "effect/Clock";
@@ -320,6 +322,7 @@
   readonly project: OrchestrationProjectShell;
   readonly api: PullRequestProviderApi;
   readonly repository: string;
+  /** The host the repository lives on. Several GitHub accounts can share one host. */
   readonly host: string;
   /**
    * Unique where `repository` is not: Azure's is a bare name that repeats across an organisation.
@@ -346,11 +349,17 @@
 }
 
 interface ListCursor extends ProviderListCursor {
+  /** The account that issued this continuation, when the provider can identify it. */
+  readonly accountId?: string;
+  /**
+   * The rows already handed over at exactly `updatedBefore`. The next read asks for that instant
+   * inclusively, so these are what keeps it from sending them a second time.
+   */
   readonly seenAt: ReadonlyArray<number>;
 }
 
 const LIST_CURSOR_PATTERN =
-  /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2}))\|(\d{1,9})\|(\d{1,9}(?:,\d{1,9})*)?(?:\|([\w.:~+-]{1,128}))?$/;
+  /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2}))\|(\d{1,9})\|(\d{1,9}(?:,\d{1,9})*)?(?:\|([a-zA-Z0-9_-]{0,64})(?:\|([\w.:~+-]{1,128}))?)?$/;
 
 function parseListCursor(raw: string): ListCursor | null {
   const match = LIST_CURSOR_PATTERN.exec(raw);
@@ -360,7 +369,8 @@
     updatedBefore: match[1]!,
     delivered: Number(match[2]),
     seenAt: seenAt === undefined ? [] : seenAt.split(",").map(Number),
-    ...(match[4] === undefined ? {} : { resumeAfter: match[4] }),
+    ...(!match[4] ? {} : { accountId: match[4] }),
+    ...(match[5] === undefined ? {} : { resumeAfter: match[5] }),
   };
 }
 
@@ -379,6 +389,7 @@
   /** A provider may consume malformed offset-paged rows that never appear in `delivered`. */
   cursorAdvance = delivered.length,
   resumeAfter?: string,
+  accountId = previous?.accountId,
 ): string | null {
   if (fetched.length === 0) return null;
   // Taken from what the host answered rather than from what survived de-duplication: a slice can
@@ -386,7 +397,7 @@
   // repository's boring afternoon — and reading "nothing new" as "nothing left" would end the
   // walk on the instant it was stuck on, with everything older unreachable for good.
   const oldest = fetched.reduce((left, right) => (right.updatedAt < left.updatedAt ? right : left));
-  return listCursorAt(previous, oldest.updatedAt, fetched, cursorAdvance, resumeAfter);
+  return listCursorAt(previous, oldest.updatedAt, fetched, cursorAdvance, resumeAfter, accountId);
 }
 
 function listCursorAt(
@@ -395,13 +406,15 @@
   fetched: ReadonlyArray<ProviderChangeRequest>,
   deliveredCount: number,
   resumeAfter?: string,
+  accountId = previous?.accountId,
 ): string {
   const seenAt = new Set([
     ...(previous?.updatedBefore === boundary ? previous.seenAt : []),
     ...fetched.filter((item) => item.updatedAt === boundary).map((item) => item.number),
   ]);
   const cursor = `${boundary}|${(previous?.delivered ?? 0) + deliveredCount}|${[...seenAt].join(",")}`;
+  if (resumeAfter !== undefined) return `${cursor}|${accountId ?? ""}|${resumeAfter}`;
+  return accountId === undefined ? cursor : `${cursor}|${accountId}`;
-  return resumeAfter === undefined ? cursor : `${cursor}|${resumeAfter}`;
 }
 
 function isProviderUnusable(error: PullRequestProviderError): boolean {
@@ -662,6 +675,7 @@
   const serverSettings = yield* ServerSettings.ServerSettingsService;
   const projects = yield* ProjectService.ProjectService;
   const repositoryIdentities = yield* RepositoryIdentityResolver.RepositoryIdentityResolver;
+  const { process } = yield* SourceControlHost.SourceControlHost;
   const sourceControlProviders = yield* SourceControlProviderRegistry.SourceControlProviderRegistry;
   const rateLimits = yield* SourceControlRateLimit.SourceControlRateLimit;
   const filesViewedStore = yield* PullRequestFilesViewed.PullRequestFilesViewedRepository;
@@ -683,7 +697,10 @@
       const identity = project.repositoryIdentity;
       if (
         (identity?.provider !== "unknown" &&
+          !(
+            (identity?.provider === "forgejo" || identity?.provider === "github") &&
+            isSshRemoteUrl(identity.locator.remoteUrl)
+          )) ||
-          !(identity?.provider === "forgejo" && isSshRemoteUrl(identity.locator.remoteUrl))) ||
         sourceControlRepositorySelector(project.repositoryIdentity) === null
       )
         continue;
@@ -713,22 +730,31 @@
       ([baseUrl, candidates]) =>
         Effect.firstSuccessOf(
           candidates.map(({ project, provider, remoteName, remoteUrl }) =>
+            Effect.gen(function* () {
+              const resolvedUrl = yield* resolveSshRemoteUrl({
+                remoteUrl,
+                cwd: project.workspaceRoot,
+                process,
+              });
+              const resolvedProvider =
+                detectSourceControlProviderFromRemoteUrl(resolvedUrl) ?? provider;
+              return yield* sourceControlProviders.resolveHandle({
-            Effect.suspend(() =>
-              sourceControlProviders.resolveHandle({
                 cwd: project.workspaceRoot,
                 context: {
                   provider:
+                    resolvedProvider.kind === "forgejo"
-                    provider.kind === "forgejo"
-                      ? { ...provider, kind: SourceControlProviderKind.make("unknown") }
+                      ? { ...resolvedProvider, kind: SourceControlProviderKind.make("unknown") }
+                      : resolvedProvider,
-                      : provider,
                   remoteName,
                   remoteUrl,
-                  ...(filter.host !== undefined && isSshRemoteUrl(remoteUrl)
+                  ...(filter.host !== undefined &&
+                  resolvedProvider.kind !== "github" &&
+                  isSshRemoteUrl(remoteUrl)
                     ? { requestedHost: filter.host }
                     : {}),
                 },
-              }),
+              });
+            }).pipe(
-            ).pipe(
               Effect.flatMap((handle) => {
                 const refined = handle.context?.provider;
                 return refined === undefined || refined.kind === "unknown"
@@ -747,6 +773,7 @@
 
   const listWorkspaceProjects = (
     filter: Pick<PullRequestListInput, "projectId" | "projectIds" | "host">,
+    options?: { readonly includeCheckouts: boolean },
   ): Effect.Effect<WorkspaceProjects, PullRequestError> =>
     (filter.projectId === undefined
       ? projects.listShells(
@@ -797,14 +824,15 @@
           let refinedProvider: SourceControlProviderInfo | null | undefined;
           if (
             kind === "unknown" ||
-            (kind === "forgejo" && isSshRemoteUrl(identity.locator.remoteUrl))
+            ((kind === "forgejo" || kind === "github") &&
+              isSshRemoteUrl(identity.locator.remoteUrl))
           ) {
             const provider = detectSourceControlProviderFromRemoteUrl(identity.locator.remoteUrl);
             refinedProvider = provider === null ? null : refinedProviders.get(provider.baseUrl);
             kind = refinedProvider?.kind ?? kind;
           }
           const host =
-            refinedProvider?.kind === "forgejo"
+            refinedProvider?.kind === "forgejo" || refinedProvider?.kind === "github"
               ? new URL(refinedProvider.baseUrl).host.toLowerCase()
               : pullRequestHostOf(identity, kind);
           if (filter.host !== undefined && host !== filter.host.toLowerCase()) {
@@ -819,7 +847,11 @@
           const repositoryKey =
             api?.repositoryKey?.({ canonicalKey: identity.canonicalKey }) ?? null;
           const key = listCursorKey(host, repositoryKey ?? repository);
-          if (seen.has(key)) continue;
+          if (
+            seen.has(key) &&
+            (options?.includeCheckouts !== true || api?.withVerifiedCredential === undefined)
+          )
+            continue;
           seen.add(key);
           if (api === null) {
             const counted = unimplemented.get(host);
@@ -991,6 +1023,10 @@
     readonly kind: SourceControlProviderKind;
     readonly viewer: string | null;
     readonly error: PullRequestProviderError | null;
+    readonly account?: {
+      readonly id: string;
+      readonly fingerprint: string;
+    };
   };
   const viewersByHost = new Map<string, { readonly at: number; readonly result: ResolvedViewer }>();
   const viewerFlights = yield* Cache.makeWith(
@@ -1034,7 +1070,7 @@
     },
   );
 
+  const resolveHostViewers = (
-  const resolveViewers = (
     projects: ReadonlyArray<SupportedProject>,
     viewerRoots: WorkspaceProjects["viewerRoots"],
     options?: { readonly allowPaused: boolean },
@@ -1081,6 +1117,120 @@
       { concurrency: REPOSITORY_CONCURRENCY },
     );
 
+  /** GitHub identifies the account in each checkout; other providers keep their host lookup. */
+  const resolveViewers = Effect.fn("PullRequestService.resolveViewers")(function* (
+    projects: ReadonlyArray<SupportedProject>,
+    viewerRoots: WorkspaceProjects["viewerRoots"],
+    options?: { readonly allowPaused: boolean },
+  ) {
+    const hostProjects = projects.filter(
+      (project) => project.api.withVerifiedCredential === undefined,
+    );
+    const hosts = yield* resolveHostViewers(hostProjects, viewerRoots, options);
+    const hostPairs = hostProjects.map(
+      (project) =>
+        [project.project.id, hosts.find((candidate) => candidate.host === project.host)!] as const,
+    );
+    const accountPairs = yield* Effect.forEach(
+      projects.filter((project) => project.api.withVerifiedCredential !== undefined),
+      (project) =>
+        project.api.withVerifiedCredential!(
+          {
+            cwd: project.project.workspaceRoot,
+            host: project.host,
+            allowReserve: options?.allowPaused === true,
+          },
+          (identity) =>
+            Effect.succeed<ResolvedViewer>({
+              host: project.host,
+              kind: project.api.kind,
+              viewer: identity.viewer,
+              error: null,
+              account: { id: identity.accountId, fingerprint: identity.credentialFingerprint },
+            }),
+        ).pipe(
+          Effect.catch((error) =>
+            Effect.succeed<ResolvedViewer>({
+              host: project.host,
+              kind: project.api.kind,
+              viewer: null,
+              error,
+            }),
+          ),
+          Effect.map((result) => [project.project.id, result] as const),
+        ),
+      { concurrency: REPOSITORY_CONCURRENCY },
+    );
+    return new Map([...hostPairs, ...accountPairs]);
+  });
+
+  /** Recheck and pin the selected account around the inbox's own backoff and provider read. */
+  const withInboxAccount = <A, E, R>(
+    project: SupportedProject,
+    viewer: ResolvedViewer | undefined,
+    read: Effect.Effect<A, E, R>,
+  ): Effect.Effect<A, E | PullRequestProviderError, R> => {
+    const account = viewer?.account;
+    const verify = project.api.withVerifiedCredential;
+    if (account === undefined || verify === undefined) return read;
+    return verify(
+      { cwd: project.project.workspaceRoot, host: project.host, allowReserve: false },
+      (identity): Effect.Effect<A, E | PullRequestProviderError, R> =>
+        identity.credentialFingerprint === account.fingerprint
+          ? read
+          : Effect.fail(
+              new PullRequestProviderError({
+                provider: project.api.kind,
+                operation: "list",
+                reason: "failed",
+                detail: "The repository's GitHub account changed. Refresh the inbox and retry.",
+              }),
+            ),
+    );
+  };
+
+  const inboxAccountKey = (project: SupportedProject, viewer: ResolvedViewer | undefined) =>
+    JSON.stringify([project.host, viewer?.account?.fingerprint ?? ""]);
+
+  /** One readable checkout per repository keeps PR rows and continuation keys unique. */
+  const selectInboxProjects = Effect.fn("PullRequestService.selectInboxProjects")(function* (
+    projects: ReadonlyArray<SupportedProject>,
+    viewerRoots: WorkspaceProjects["viewerRoots"],
+  ) {
+    const hostViewers = yield* resolveViewers(
+      projects.filter((project) => project.api.withVerifiedCredential === undefined),
+      viewerRoots,
+    );
+    const groups = new Map<string, Array<SupportedProject>>();
+    for (const project of projects) {
+      const held = groups.get(project.cursorKey);
+      if (held === undefined) groups.set(project.cursorKey, [project]);
+      else held.push(project);
+    }
+    return yield* Effect.forEach(
+      groups.values(),
+      (group) => {
+        const first = group[0]!;
+        if (first.api.withVerifiedCredential === undefined)
+          return Effect.succeed({ project: first, viewer: hostViewers.get(first.project.id)! });
+        return Effect.gen(function* () {
+          let fallback:
+            | { readonly project: SupportedProject; readonly viewer: ResolvedViewer }
+            | undefined;
+          for (const project of group) {
+            const viewers = yield* resolveViewers([project], viewerRoots);
+            const viewer = viewers.get(project.project.id)!;
+            const result = { project, viewer };
+            fallback ??= result;
+            if (viewer.error === null) return result;
+          }
+          return fallback!;
+        });
+      },
+      { concurrency: REPOSITORY_CONCURRENCY },
+    );
+  });
+
   /**
    * `checks` is absent because no listed row carries its check state: that one filter is
    * the host's alone, and a row nobody narrowed stays rather than being guessed at.
@@ -1136,6 +1286,7 @@
       createdAt: input.item.createdAt,
       updatedAt: input.item.updatedAt,
       observedAt: input.observedAt,
+      ...(input.viewer.trim() === "" ? {} : { viewer: input.viewer }),
       ...(input.item.checksState === undefined || input.item.checksState === null
         ? {}
         : { checksState: input.item.checksState }),
@@ -1153,30 +1304,49 @@
   // authored/reviewing searches for that same repository are therefore real empty answers, not
   // a reason to issue the two-command per-repository fallback again.
   const searchVisibleAt = new Map<string, number>();
-  const searchVisibilityKey = (host: string, repository: string) =>
-    `${host}\n${repository.trim().toLowerCase()}`;
+  const searchVisibilityKey = (account: string, repository: string) =>
+    `${account}\n${repository.trim().toLowerCase()}`;
 
   const listUncached: PullRequestService["Service"]["list"] = Effect.fn(
     "PullRequestService.listUncached",
   )(function* (input) {
     const involvement = input.involvement ?? "all";
+    // Refused whole rather than per repository: a cursor is only ever a value this service
+    // issued, so one that does not read as one means the page is sending something it made up,
+    // and reading part of the listing under that assumption would quietly lose rows.
     const continuation = yield* decodeCursors(input.cursors);
+    const {
+      supported: checkouts,
+      unimplemented,
+      viewerRoots,
+    } = yield* listWorkspaceProjects(input, { includeCheckouts: true });
+    const selectedProjects = yield* selectInboxProjects(checkouts, viewerRoots);
+    const projects = selectedProjects.map(({ project }) => project);
+    const viewerResults = new Map(
+      selectedProjects.map(({ project, viewer }) => [project.project.id, viewer]),
+    );
-    const { supported: projects, unimplemented, viewerRoots } = yield* listWorkspaceProjects(input);
     const projectCounts = new Map<string, number>();
     for (const { host } of projects) {
       projectCounts.set(host, (projectCounts.get(host) ?? 0) + 1);
     }
 
-    const viewerResults = yield* resolveViewers(projects, viewerRoots);
     const viewers: Record<string, string> = {};
+    for (const result of viewerResults.values()) {
-    for (const result of viewerResults) {
-      if (result.viewer !== null) viewers[result.host] = result.viewer;
+      if (result.viewer !== null && viewers[result.host] === undefined)
+        viewers[result.host] = result.viewer;
     }
 
+    // One summary per host, which is what the viewer lookup already answers for: two GitHub
+    // hosts sign in separately, so collapsing them by kind would report one as the other.
     const providers: ReadonlyArray<PullRequestProviderSummary> = [
+      ...[...projectCounts.keys()].map((host) => {
+        const onHost = projects.filter((project) => project.host === host);
+        const results = onHost.flatMap((project) => {
+          const result = viewerResults.get(project.project.id);
+          return result === undefined ? [] : [result];
+        });
-      ...viewerResults.map((result) => {
-        const capabilities = projects.find((project) => project.host === result.host)?.api
-          .capabilities;
+        const result = results.find((candidate) => candidate.viewer !== null) ?? results[0]!;
+        const capabilities = onHost[0]?.api.capabilities;
         return {
           host: result.host,
           kind: result.kind,
@@ -1198,24 +1368,52 @@
       })),
     ];
 
+    // A continued listing reads only the repositories it was asked to carry on with: every
+    // other one is already on the page, and reading it again is the whole cost this is here to
+    // avoid. The host summaries above stay over the whole workspace, because the switcher they
+    // fill is about the workspace rather than about this slice.
     const selected =
       continuation === null
         ? projects
         : projects.filter(({ cursorKey }) => continuation.has(cursorKey));
-    const readable = selected.filter(({ host }) => viewers[host] !== undefined);
+    for (const project of selected) {
+      const previousAccount = continuation?.get(project.cursorKey)?.accountId;
+      const account = viewerResults.get(project.project.id)?.account;
+      if (previousAccount !== undefined && account !== undefined && previousAccount !== account.id)
+        return yield* new PullRequestOperationError({
+          operation: "list",
+          detail: "The repository's GitHub account changed. Refresh the inbox to continue.",
+        });
+    }
+    const readable = selected.filter(
+      (project) => viewerResults.get(project.project.id)?.viewer != null,
+    );
+    // A host that could not be read still has projects, and they are absent from the list.
+    // Reporting them keeps "N repositories were unavailable" honest instead of dropping them.
     const unreadable = selected
+      .filter((project) => viewerResults.get(project.project.id)?.viewer == null)
+      .map((candidate) => ({
+        projectId: candidate.project.id,
+        projectTitle: candidate.project.title,
+        message:
+          candidate.api.withVerifiedCredential === undefined
+            ? `${candidate.repository} could not be read.`
+            : (viewerResults.get(candidate.project.id)?.error?.detail ??
+              `${candidate.repository} could not be read.`),
-      .filter(({ host }) => viewers[host] === undefined)
-      .map(({ project, repository }) => ({
-        projectId: project.id,
-        projectTitle: project.title,
-        message: `${repository} could not be read.`,
       }));
     if (readable.length === 0) {
+      // No host this request covers can be read, so it is not a per-project problem. An
+      // unusable host is preferred as the reported cause because it names the fix; a host
+      // that merely failed reports as a failed operation rather than as a signed-out CLI,
+      // which would send the reader to `auth login` over a transient error.
+      //
+      // Only the hosts this request was actually going to read: a continuation that named
+      // nothing has asked for nothing, and a host it never mentioned being signed out is no
+      // reason to refuse it.
+      const errors = selected.flatMap((project) => {
+        const error = viewerResults.get(project.project.id)?.error;
+        return error == null ? [] : [error];
-      const errors = viewerResults.flatMap((result) =>
+      });
-        result.error === null || !selected.some(({ host }) => host === result.host)
-          ? []
-          : [result.error],
-      );
       const blocking = errors.find(isProviderUnusable) ?? errors[0];
       if (blocking) {
         return yield* toPullRequestError("list")(blocking);
@@ -1234,16 +1432,18 @@
     const limit = input.limit ?? DEFAULT_REPOSITORY_LIST_LIMIT;
     const cursorOf = (project: SupportedProject): ListCursor | undefined =>
       continuation?.get(project.cursorKey);
-
     const readSlice = (
       project: SupportedProject,
       cursor: ListCursor | undefined,
     ): Effect.Effect<RepositoryBatch> => {
       {
+        const identity = viewerResults.get(project.project.id)!;
+        const viewer = identity.viewer!;
-        const viewer = viewers[project.host]!;
         const key = project.cursorKey;
-        return project.api
-          .listChangeRequests({
+        return withInboxAccount(
+          project,
+          identity,
+          project.api.listChangeRequests({
             cwd: project.project.workspaceRoot,
             repository: project.repository,
             host: project.host,
@@ -1263,54 +1463,55 @@
                     resumeAfter: cursor.resumeAfter,
                   },
                 }),
-          })
+          }),
+        ).pipe(
+          observeRead,
+          Effect.map(({ value: page, observedAt }): RepositoryBatch => {
+            // The boundary instant was asked for inclusively, so the rows already sent at it
+            // come back with the slice. Dropping them here rather than asking for strictly
+            // older is what keeps their neighbours at the same instant from being skipped.
+            const items =
+              cursor === undefined
-          .pipe(
+                ? page.items
+                : page.items.filter(
+                    (item) =>
+                      item.updatedAt !== cursor.updatedBefore ||
+                      !cursor.seenAt.includes(item.number),
+                  );
+            return {
-            observeRead,
-            Effect.map(({ value: page, observedAt }): RepositoryBatch => {
-              // The boundary instant was asked for inclusively, so the rows already sent at it
-              // come back with the slice. Dropping them here rather than asking for strictly
-              // older is what keeps their neighbours at the same instant from being skipped.
-              const items =
-                cursor === undefined
-                  ? page.items
-                  : page.items.filter(
-                      (item) =>
-                        item.updatedAt !== cursor.updatedBefore ||
-                        !cursor.seenAt.includes(item.number),
-                    );
-              return {
-                key,
-                entries: items
-                  .filter((item) => matchesRowFilters(item, input.filters, viewer))
-                  .map((item) => toEntry({ project, item, viewer, observedAt })),
-                errors: [],
-                truncated: page.truncated,
-                nextCursor:
-                  page.continues && page.truncated
-                    ? nextListCursor(
-                        cursor,
-                        page.items,
-                        items,
-                        page.cursorAdvance,
-                        page.resumeAfter,
-                      )
-                    : null,
-              };
-            }),
-            Effect.orElseSucceed((): RepositoryBatch => ({
               key,
+              entries: items
+                .filter((item) => matchesRowFilters(item, input.filters, viewer))
+                .map((item) => toEntry({ project, item, viewer, observedAt })),
+              errors: [],
+              truncated: page.truncated,
+              nextCursor:
+                page.continues && page.truncated
+                  ? nextListCursor(
+                      cursor,
+                      page.items,
+                      items,
+                      page.cursorAdvance,
+                      page.resumeAfter,
+                      identity.account?.id,
+                    )
+                  : null,
+            };
-              entries: [],
-              errors: [
-                {
-                  projectId: project.project.id,
-                  projectTitle: project.project.title,
-                  message: `${project.repository} could not be read.`,
-                },
-              ],
-              truncated: false,
-              nextCursor: null,
-            })),
-          );
+          }),
+          Effect.orElseSucceed((): RepositoryBatch => ({
+            key,
+            entries: [],
+            errors: [
+              {
+                projectId: project.project.id,
+                projectTitle: project.project.title,
+                message: `${project.repository} could not be read.`,
+              },
+            ],
+            truncated: false,
+            nextCursor: null,
+          })),
+        );
       }
     };
 
@@ -1348,22 +1549,28 @@
       const separately = () =>
         Effect.forEach(chunk, readRepository, { concurrency: REPOSITORY_CONCURRENCY });
       if (readAcross === undefined) return separately();
-      const viewer = viewers[first.host]!;
+      const identity = viewerResults.get(first.project.id)!;
+      const viewer = identity.viewer!;
+      const account = inboxAccountKey(first, identity);
       const cursor = cursorOf(first);
+      return withInboxAccount(
+        first,
+        identity,
+        readAcross({
+          cwd: first.project.workspaceRoot,
+          host: first.host,
+          repositories: chunk.map((project) => project.repository),
+          state: input.state,
+          involvement,
+          viewer,
+          limit,
+          query: input.query,
+          filters: input.filters,
+          ...(cursor === undefined
+            ? {}
+            : { cursor: { updatedBefore: cursor.updatedBefore, delivered: cursor.delivered } }),
+        }),
+      ).pipe(
-      return readAcross({
-        cwd: first.project.workspaceRoot,
-        host: first.host,
-        repositories: chunk.map((project) => project.repository),
-        state: input.state,
-        involvement,
-        viewer,
-        limit,
-        query: input.query,
-        filters: input.filters,
-        ...(cursor === undefined
-          ? {}
-          : { cursor: { updatedBefore: cursor.updatedBefore, delivered: cursor.delivered } }),
-      }).pipe(
         observeRead,
         Effect.flatMap(({ value: page, observedAt }) =>
           Effect.flatMap(Clock.currentTimeMillis, (now) => {
@@ -1378,8 +1585,10 @@
               const held = rows.get(key);
               if (held === undefined) rows.set(key, [item]);
               else held.push(item);
-              searchVisibleAt.set(searchVisibilityKey(first.host, item.repository), now);
+              searchVisibleAt.set(searchVisibilityKey(account, item.repository), now);
             }
+            // The oldest row of the whole slice, which is how far every repository in it has now
+            // been read — including the ones that contributed nothing to it.
             const boundary = page.items.reduce<string | null>(
               (oldest, item) =>
                 oldest === null || item.updatedAt < oldest ? item.updatedAt : oldest,
@@ -1393,9 +1602,12 @@
                 // its old name with silence rather than with an error — so a repository the
                 // search said nothing at all about is read on its own, once, before it is
                 // believed. Only on its first slice: after that it has a boundary to carry on
-                // from, and silence past one means the rows are older rather than absent.
+                // from, and silence past one means the rows are older rather than absent. That
+                // keeps a search-invisible repository from disappearing on a busy host, at the
+                // price of one request per repository with nothing in the first slice — which
+                // run together, and only there.
                 const lastVisible = searchVisibleAt.get(
-                  searchVisibilityKey(project.host, project.repository),
+                  searchVisibilityKey(account, project.repository),
                 );
                 const searchIsKnownVisible =
                   !page.truncated &&
@@ -1426,7 +1638,14 @@
                   truncated: page.truncated,
                   nextCursor:
                     page.truncated && boundary !== null
-                      ? listCursorAt(cursorHere, boundary, fetched, items.length)
+                      ? listCursorAt(
+                          cursorHere,
+                          boundary,
+                          fetched,
+                          items.length,
+                          undefined,
+                          identity.account?.id,
+                        )
                       : null,
                 });
               },
@@ -1438,6 +1657,9 @@
       );
     };
 
+    // A host with a search across repositories is asked once for all of them; everyone else is
+    // asked once each. Repositories standing at different points of the same listing are
+    // different questions, so they are grouped by the boundary they carry on from.
     const together = new Map<string, Array<SupportedProject>>();
     const separate: Array<SupportedProject> = [];
     for (const project of readable) {
@@ -1445,7 +1667,7 @@
         separate.push(project);
         continue;
       }
+      const key = `${inboxAccountKey(project, viewerResults.get(project.project.id))}\n${cursorOf(project)?.updatedBefore ?? ""}`;
-      const key = `${project.host}\n${cursorOf(project)?.updatedBefore ?? ""}`;
       const group = together.get(key);
       if (group === undefined) together.set(key, [project]);
       else group.push(project);
@@ -1477,13 +1699,18 @@
     };
   });
 
+  /**
+   * Who this project's host says the reader is. Shared with the listing's own lookup — the same
+   * ten-minute answer per host — so a page that has already listed anything pays nothing for it,
+   * and a host that cannot say leaves it null rather than failing the read it decorates.
+   */
   const viewerOf = (project: SupportedProject): Effect.Effect<string | null> =>
     routingCredential.pipe(
       Effect.flatMap((credential) =>
         credential !== null
           ? Effect.succeed(credential.viewer)
           : resolveViewers([project], new Map()).pipe(
-              Effect.map(([resolved]) => resolved?.viewer ?? null),
+              Effect.map((resolved) => resolved.get(project.project.id)?.viewer ?? null),
             ),
       ),
     );
@@ -1871,7 +2098,8 @@
     operation: string,
   ): Effect.Effect<string | null, PullRequestError> =>
     resolveViewers([project], new Map(), { allowPaused: true }).pipe(
-      Effect.flatMap(([resolved]) => {
+      Effect.flatMap((results) => {
+        const resolved = results.get(project.project.id);
         const error = resolved?.error ?? null;
         return error === null
           ? Effect.succeed(resolved?.viewer ?? null)
@@ -2521,7 +2749,7 @@
     "PullRequestService.listStatsUncached",
   )(function* (input) {
     if (input.refs.length === 0) return { stats: [] };
+    const { supported } = yield* listWorkspaceProjects({}, { includeCheckouts: true });
-    const { supported } = yield* listWorkspaceProjects({});
     const byProject = new Map(supported.map((project) => [project.project.id, project]));
     const wanted = new Map<
       string,
@@ -2529,6 +2757,8 @@
     >();
     for (const ref of input.refs) {
       const project = byProject.get(ref.projectId);
+      // The repository travels through the client, so it is checked against the project's own
+      // remote rather than being handed to a provider verbatim.
       if (
         project === undefined ||
         project.api.listChangeRequestStats === undefined ||
@@ -2538,50 +2768,61 @@
       }
       wanted.set(`${project.project.id} ${ref.number}`, { project, number: ref.number });
     }
-    const byHost = new Map<string, Array<{ project: SupportedProject; number: number }>>();
+    const viewers = yield* resolveViewers(
+      [...new Set([...wanted.values()].map(({ project }) => project))].filter(
+        (project) => project.api.withVerifiedCredential !== undefined,
+      ),
+      new Map(),
+    );
+    const byAccount = new Map<string, Array<{ project: SupportedProject; number: number }>>();
     for (const entry of wanted.values()) {
-      const held = byHost.get(entry.project.host);
-      if (held === undefined) byHost.set(entry.project.host, [entry]);
+      const identity = viewers.get(entry.project.project.id);
+      if (identity?.error != null) continue;
+      const key = inboxAccountKey(entry.project, identity);
+      const held = byAccount.get(key);
+      if (held === undefined) byAccount.set(key, [entry]);
       else held.push(entry);
     }
     const stats = yield* Effect.forEach(
+      [...byAccount.values()],
-      [...byHost.values()],
       (entries) => {
         const first = entries[0]!;
         const readStats = first.project.api.listChangeRequestStats;
         if (readStats === undefined) return Effect.succeed<ReadonlyArray<PullRequestDiffStat>>([]);
+        const references = new Map<string, Array<(typeof entries)[number]>>();
+        for (const entry of entries) {
+          const key = `${entry.project.repository.toLowerCase()} ${entry.number}`;
+          const held = references.get(key);
+          if (held === undefined) references.set(key, [entry]);
+          else held.push(entry);
+        }
+        return withInboxAccount(
+          first.project,
+          viewers.get(first.project.project.id),
+          readStats({
+            cwd: first.project.project.workspaceRoot,
+            host: first.project.host,
-        const projectsByRepository = new Map(
-          entries.map((entry) => [
-            `${entry.project.repository.toLowerCase()} ${entry.number}`,
-            entry.project,
-          ]),
-        );
-        return readStats({
-          cwd: first.project.project.workspaceRoot,
-          host: first.project.host,
-          changeRequests: entries.map((entry) => ({
-            repository: entry.project.repository,
-            number: entry.number,
-          })),
-        }).pipe(
+            changeRequests: [...references.values()].map(([entry]) => ({
+              repository: entry!.project.repository,
+              number: entry!.number,
+            })),
+          }),
+        ).pipe(
           Effect.map((read) =>
+            read.flatMap((stat): ReadonlyArray<PullRequestDiffStat> =>
+              (references.get(`${stat.repository.toLowerCase()} ${stat.number}`) ?? []).map(
+                ({ project }) => ({
+                  projectId: project.project.id,
+                  repository: project.repository,
-            read.flatMap((stat): ReadonlyArray<PullRequestDiffStat> => {
-              const project = projectsByRepository.get(
+                  number: stat.number,
+                  additions: stat.additions,
+                  deletions: stat.deletions,
+                }),
+              ),
+            ),
-                `${stat.repository.toLowerCase()} ${stat.number}`,
-              );
-              return project === undefined
-                ? []
-                : [
-                    {
-                      projectId: project.project.id,
-                      repository: project.repository,
-                      number: stat.number,
-                      additions: stat.additions,
-                      deletions: stat.deletions,
-                    },
-                  ];
-            }),
           ),
+          // A row without its counts is a row the page already draws without them, so a host
+          // that could not answer costs the numbers rather than the answer.
           Effect.orElseSucceed((): ReadonlyArray<PullRequestDiffStat> => []),
         );
       },
```

## apps/server/src/sourceControl/SourceControlProviderRegistry.test.ts

Status: modified; +46; −0

```diff
@@ -383,6 +383,48 @@
   }),
 );
 
+it.effect("detects the configured SSH hostname while preserving the transport alias", () =>
+  Effect.gen(function* () {
+    const remoteUrl = "git@github.com-work:example/project.git";
+    const registry = yield* makeRegistry({
+      remotes: [{ name: "origin", url: remoteUrl }],
+      process: {
+        run: (input) =>
+          Effect.succeed(processOutput(input.command === "ssh" ? "hostname github.com\n" : "")),
+      },
+    });
+    const handle = yield* registry.resolveHandle({ cwd: "/repo" });
+    assert.strictEqual(handle.context?.provider.kind, "github");
+    assert.strictEqual(handle.context?.provider.name, "GitHub");
+    assert.strictEqual(handle.context?.provider.baseUrl, "https://github.com");
+    assert.strictEqual(handle.context?.remoteUrl, remoteUrl);
+  }),
+);
+
+it.effect("preserves an explicit web authority when its SSH transport uses another host", () =>
+  Effect.gen(function* () {
+    const registry = yield* makeRegistry({
+      remotes: [],
+      process: {
+        run: () => Effect.die("An explicit web authority must not be replaced by SSH discovery"),
+      },
+    });
+    const context = {
+      provider: {
+        kind: SourceControlProviderKind.make("forgejo"),
+        name: "Forgejo",
+        baseUrl: "https://reviews.example.test/gitea",
+      },
+      requestedHost: "reviews.example.test",
+      remoteName: "origin",
+      remoteUrl: "git@work:team/project.git",
+    };
+    const handle = yield* registry.resolveHandle({ cwd: "/repo with spaces", context });
+    assert.deepStrictEqual(handle.context, context);
+    assert.strictEqual(handle.provider.kind, "forgejo");
+  }),
+);
+
 const notFound = (command: string, cwd: string) =>
   new VcsProcessSpawnError({
     operation: "test",
@@ -416,6 +458,8 @@
       remotes: [],
       process: {
         run: ({ command, args, cwd }) => {
+          if (command === "ssh")
+            return Effect.succeed(processOutput("hostname git.example.internal\n"));
           asked.push([command, ...args].join(" "));
           return Deferred.await(release).pipe(Effect.andThen(Effect.fail(notFound(command, cwd))));
         },
@@ -473,6 +517,8 @@
       remotes: [],
       process: {
         run: ({ command, cwd }) => {
+          if (command === "ssh")
+            return Effect.succeed(processOutput("hostname git.example.internal\n"));
           asks += 1;
           return Effect.fail(
             new VcsProcessTimeoutError({ operation: "test", command, cwd, timeoutMs: 1 }),
```

## apps/server/src/sourceControl/SourceControlProviderRegistry.ts

Status: modified; +39; −6

```diff
@@ -24,6 +24,7 @@
   type UnknownRemoteVerdict,
 } from "@t3tools/source-control-core/server/discovery";
 import * as SourceControlHost from "@t3tools/source-control-core/server/SourceControlHost";
+import { resolveSshRemoteUrl } from "@t3tools/source-control-core/server/sshRemote";
 
 import * as ServerConfig from "../config.ts";
 import * as ServerSettings from "../serverSettings.ts";
@@ -140,11 +141,12 @@
   remotes: ReadonlyArray<{
     readonly name: string;
     readonly url: string;
+    readonly resolvedUrl?: string;
   }>,
 ): SourceControlProvider.SourceControlProviderContext | null {
   const candidates: Array<SourceControlProvider.SourceControlProviderContext> = [];
   for (const remote of remotes) {
-    const provider = detectSourceControlProviderFromRemoteUrl(remote.url);
+    const provider = detectSourceControlProviderFromRemoteUrl(remote.resolvedUrl ?? remote.url);
     if (provider) {
       candidates.push({
         provider,
@@ -304,7 +306,14 @@
               }),
           ),
         );
+        const resolvedRemotes = yield* Effect.forEach(remotes.remotes, (remote) =>
+          resolveSshRemoteUrl({ remoteUrl: remote.url, cwd, process }).pipe(
+            Effect.map((resolvedUrl) => ({ ...remote, resolvedUrl })),
+          ),
+        );
+        const context = selectProviderContext(resolvedRemotes);
+
+        return yield* refineContext(cwd, context);
-        return yield* refineContext(cwd, selectProviderContext(remotes.remotes));
       },
     );
 
@@ -317,10 +326,33 @@
       timeToLive: (exit) => (Exit.isSuccess(exit) ? PROVIDER_DETECTION_CACHE_TTL : Duration.zero),
     });
 
+    const resolveHandle: SourceControlProviderRegistry["Service"]["resolveHandle"] = (input) => {
+      const requestedContext = input.context;
+      return (
+        requestedContext === undefined
+          ? Cache.get(providerContextCache, input.cwd)
+          : (requestedContext.requestedHost === undefined
+              ? resolveSshRemoteUrl({
+                  remoteUrl: requestedContext.remoteUrl,
+                  cwd: input.cwd,
+                  process,
+                })
+              : Effect.succeed(requestedContext.remoteUrl)
+            ).pipe(
+              Effect.flatMap((url) =>
+                refineContext(
+                  input.cwd,
-    const resolveHandle: SourceControlProviderRegistry["Service"]["resolveHandle"] = (input) =>
+                  url === requestedContext.remoteUrl
-      (input.context === undefined
-        ? Cache.get(providerContextCache, input.cwd)
-        : refineContext(input.cwd, input.context)
+                    ? requestedContext
+                    : {
+                        ...requestedContext,
+                        provider:
+                          detectSourceControlProviderFromRemoteUrl(url) ??
+                          requestedContext.provider,
+                      },
+                ),
+              ),
+            )
       ).pipe(
         Effect.map((context) => {
           const kind = context?.provider.kind ?? SourceControlProviderKind.make("unknown");
@@ -331,6 +363,7 @@
           } satisfies SourceControlProviderHandle;
         }),
       );
+    };
 
     return SourceControlProviderRegistry.of({
       resolveLink: (input) => {
```

## apps/web/src/components/pullRequest/pullRequestList.logic.test.ts

Status: modified; +39; −0

```diff
@@ -8,7 +8,9 @@
 import { describe, expect, it } from "vite-plus/test";
 
 import {
+  matchesPullRequestFilters,
   pullRequestHostActions,
+  pullRequestEntryViewer,
   largestPullRequestReadCount,
   pullRequestQuickActions,
   filterPullRequestsByInvolvement,
@@ -722,6 +724,43 @@
   });
 });
 
+it("uses each row account for Authored and author:me on one host", () => {
+  const rows = [
+    entry({
+      number: 501,
+      viewer: "work-user",
+      author: { login: "work-user", name: null, avatarUrl: null },
+    }),
+    entry({
+      number: 502,
+      viewer: "personal-user",
+      author: { login: "personal-user", name: null, avatarUrl: null },
+    }),
+    entry({
+      number: 503,
+      viewer: "personal-user",
+      author: { login: "work-user", name: null, avatarUrl: null },
+    }),
+  ];
+  const viewers = { "github.com": "work-user" };
+  expect(
+    filterPullRequestsByInvolvement(rows, viewers, "authored").map((row) => row.number),
+  ).toEqual([501, 502]);
+  expect(
+    rows
+      .filter((row) =>
+        matchesPullRequestFilters(row, { author: "me" }, pullRequestEntryViewer(row, viewers)!),
+      )
+      .map((row) => row.number),
+  ).toEqual([501, 502]);
+  expect(
+    groupPullRequestsByInvolvement(rows, viewers)
+      .find((group) => group.key === "authored")
+      ?.entries.map((row) => row.number),
+  ).toEqual([501, 502]);
+  expect(pullRequestEntryViewer(entry({ number: 504 }), viewers)).toBe("work-user");
+});
+
 describe("refresh page size", () => {
   const twoPagesAcrossFourRepositories = (provider: "github" | "gitlab") =>
     ["a", "b", "c", "d"].flatMap((name) =>
```

## apps/web/src/components/pullRequest/pullRequestList.logic.ts

Status: modified; +3; −1

```diff
@@ -211,7 +211,9 @@
   entry: ScopedEntry,
   viewers: PullRequestViewers,
 ): string | null {
-  return normalize(viewers[pullRequestViewerKey(entry)] ?? viewers[entry.host]);
+  // The environment's own answer first; a plain host key is what a single-environment listing
+  // still writes, and what the snapshot from one carries.
+  return normalize(entry.viewer ?? viewers[pullRequestViewerKey(entry)] ?? viewers[entry.host]);
 }
 
 function isAuthoredByViewer(entry: ScopedEntry, viewers: PullRequestViewers): boolean {
```

## packages/contracts/src/pullRequest.test.ts

Status: modified; +14; −0

```diff
@@ -61,6 +61,20 @@
 };
 
 describe("PullRequestListResult", () => {
+  it("round-trips per-entry accounts while accepting older host-only results", () => {
+    const result = {
+      ...LIST_RESULT,
+      entries: [
+        { ...LIST_RESULT.entries[0]!, viewer: "work-user" },
+        { ...LIST_RESULT.entries[0]!, number: 2, viewer: "personal-user" },
+      ],
+    };
+    const codec = Schema.toCodecJson(PullRequestListResult);
+    expect(Schema.decodeSync(codec)(Schema.encodeUnknownSync(codec)(result))).toStrictEqual(result);
+    expect(
+      Schema.decodeUnknownSync(PullRequestListResult)(LIST_RESULT).entries[0]?.viewer,
+    ).toBeUndefined();
+  });
   it("separates Forgejo HTTP ports while preserving other provider host identities", () => {
     const identity = {
       canonicalKey: "forge.example/team/repo",
```

## packages/contracts/src/pullRequest.ts

Status: modified; +14; −2

```diff
@@ -318,6 +318,9 @@
 export const PullRequestListEntry = Schema.Struct({
   stack: Schema.optional(PullRequestStackMembership),
   provider: SourceControlProviderKind,
+  /**
+   * The host below which `repository` is addressed. Accounts on that host are selected per checkout.
+   */
   host: TrimmedNonEmptyString,
   projectId: ProjectId,
   projectTitle: TrimmedNonEmptyString,
@@ -340,6 +343,8 @@
   createdAt: IsoDateTime,
   updatedAt: IsoDateTime,
   observedAt: Schema.optional(Schema.Finite),
+  /** The checkout's signed-in login. Older servers only provide the host-wide viewer map. */
+  viewer: Schema.optional(TrimmedNonEmptyString),
   viewerReviewRequested: Schema.Boolean,
   labels: Schema.Array(PullRequestLabel),
   reviewDecision: Schema.optional(PullRequestReviewDecision),
@@ -347,6 +352,13 @@
 });
 export type PullRequestListEntry = typeof PullRequestListEntry.Type;
 
+/**
+ * Where each repository a listing already reached carries on from, keyed "<host> <repository>".
+ * A repository uses its first readable checkout's account within a listing.
+ *
+ * Each value is opaque: only the provider that issued one knows what it means, and the page hands
+ * back exactly what it was given rather than composing one.
+ */
 export const PullRequestListCursors = Schema.Record(
   TrimmedNonEmptyString,
   TrimmedNonEmptyString.check(Schema.isMaxLength(4096)),
@@ -386,8 +398,8 @@
 
 export const PullRequestListResult = Schema.Struct({
   /**
-   * A host that could not be read is absent rather than present-and-undefined, because an
-   * open-keyed record cannot carry an optional value through the JSON codec.
+   * A fallback signed-in login per host for older list entries. An entry's own viewer takes
+   * precedence when present. Hosts with no readable account are absent.
    */
   viewers: Schema.Record(TrimmedNonEmptyString, TrimmedNonEmptyString),
   providers: Schema.Array(PullRequestProviderSummary),
```

## packages/source-control-core/package.json

Status: modified; +4; −0

```diff
@@ -42,6 +42,10 @@
     "./server/SourceControlRateLimit": {
       "types": "./src/server/SourceControlRateLimit.ts",
       "import": "./src/server/SourceControlRateLimit.ts"
+    },
+    "./server/sshRemote": {
+      "types": "./src/server/sshRemote.ts",
+      "import": "./src/server/sshRemote.ts"
     }
   },
   "scripts": {
```

## packages/source-control-core/src/server/PullRequestProvider.ts

Status: modified; +1; −1

```diff
@@ -271,7 +271,7 @@
 
 export interface PullRequestProviderApi {
   readonly withVerifiedCredential?: <A, E, R>(
-    input: { readonly cwd: string; readonly host: string },
+    input: { readonly cwd: string; readonly host: string; readonly allowReserve?: boolean },
     use: (identity: {
       readonly accountId: string;
       readonly viewer: string;
```

## packages/source-control-core/src/server/sshRemote.test.ts

Status: added; +234; −0

```diff
@@ -1,0 +1,234 @@
+import { VcsProcessTimeoutError } from "@t3tools/contracts";
+import { expect, it } from "@effect/vitest";
+import * as TestClock from "effect/testing/TestClock";
+import * as Effect from "effect/Effect";
+import { ChildProcessSpawner } from "effect/process";
+import { resolveSshRemoteUrl } from "./sshRemote.ts";
+
+it.effect("resolves GitHub.com aliases and preserves other hosting endpoints", () =>
+  Effect.gen(function* () {
+    for (const [remoteUrl, hostname, expected] of [
+      [
+        "git@github.com-work:example/project.git",
+        "github.com",
+        "git@github.com:example/project.git",
+      ],
+      [
+        "ssh://git@work:2222/example/project.git",
+        "github.example.test",
+        "ssh://git@work:2222/example/project.git",
+      ],
+      ["git@work:example/project.git", "gitlab.com", "git@work:example/project.git"],
+      [
+        "ssh://git@work:2222/group/../project%20name.git",
+        "github.com",
+        "ssh://git@github.com:2222/group/../project%20name.git",
+      ],
+      ["git@work:group/project name.git", "github.com", "git@github.com:group/project name.git"],
+    ] as const) {
+      const result = yield* resolveSshRemoteUrl({
+        remoteUrl,
+        cwd: "/repo",
+        process: {
+          run: () =>
+            Effect.succeed({
+              stdout: `hostname ${hostname}\n`,
+              stderr: "",
+              exitCode: ChildProcessSpawner.ExitCode(0),
+              stdoutTruncated: false,
+              stderrTruncated: false,
+            }),
+        },
+      });
+      expect(result).toBe(expected);
+    }
+  }),
+);
+
+it.effect("leaves HTTPS remotes alone and keeps SSH URLs when resolution fails", () =>
+  Effect.gen(function* () {
+    const remoteUrl = "https://github.com-work/example/project.git";
+    const result = yield* resolveSshRemoteUrl({
+      remoteUrl,
+      cwd: "/repo",
+      process: { run: () => Effect.die("HTTPS must not invoke ssh") },
+    });
+    expect(result).toBe(remoteUrl);
+    const ssh = "git@github.com-work:example/project.git";
+    expect(
+      yield* resolveSshRemoteUrl({
+        remoteUrl: ssh,
+        cwd: "/repo",
+        process: {
+          run: () =>
+            Effect.fail(
+              new VcsProcessTimeoutError({
+                operation: "test",
+                command: "ssh",
+                cwd: "/repo",
+                timeoutMs: 5000,
+              }),
+            ),
+        },
+      }),
+    ).toBe(ssh);
+  }),
+);
+
+it.effect("keeps the original remote when SSH has no usable hostname", () =>
+  Effect.gen(function* () {
+    const remoteUrl = "ssh://git@work:2222/example/project.git";
+    for (const [stdout, exitCode] of [
+      ["user git\n", 0],
+      ["hostname -bad-host\n", 0],
+      ["hostname github.com\n", 1],
+    ] as const) {
+      expect(
+        yield* resolveSshRemoteUrl({
+          remoteUrl,
+          cwd: "/repo with spaces",
+          process: {
+            run: () =>
+              Effect.succeed({
+                stdout,
+                stderr: "",
+                exitCode: ChildProcessSpawner.ExitCode(exitCode),
+                stdoutTruncated: false,
+                stderrTruncated: false,
+              }),
+          },
+        }),
+      ).toBe(remoteUrl);
+    }
+  }),
+);
+
+it.effect("reads the SSH URL's user, alias, and transport port", () =>
+  Effect.gen(function* () {
+    const remoteUrl = "ssh://git@github.com-work:2222/example/project.git";
+    const result = yield* resolveSshRemoteUrl({
+      remoteUrl,
+      cwd: "/repo with spaces",
+      process: {
+        run: (input) => {
+          expect(input.args).toEqual(["-G", "-l", "git", "-p", "2222", "github.com-work"]);
+          return Effect.succeed({
+            stdout: "hostname github.com\n",
+            stderr: "",
+            exitCode: ChildProcessSpawner.ExitCode(0),
+            stdoutTruncated: false,
+            stderrTruncated: false,
+          });
+        },
+      },
+    });
+    expect(result).toBe("ssh://git@github.com:2222/example/project.git");
+  }),
+);
+
+it.effect("keeps public API hosts when SSH uses an alternate endpoint or IP address", () =>
+  Effect.gen(function* () {
+    for (const hostname of ["github.com", "gitlab.com", "bitbucket.org"]) {
+      const remoteUrl = `git@${hostname}:example/project.git`;
+      expect(
+        yield* resolveSshRemoteUrl({
+          remoteUrl,
+          cwd: "/repo",
+          process: { run: () => Effect.die("Known API hosts do not need SSH resolution") },
+        }),
+      ).toBe(remoteUrl);
+    }
+    for (const [hostname, expectedHost] of [
+      ["ssh.github.com", "github.com"],
+      ["altssh.gitlab.com", "work"],
+      ["altssh.bitbucket.org", "work"],
+      ["ssh.github.example.test", "work"],
+      ["gitea.example.test", "work"],
+      ["192.0.2.1", "work"],
+    ]) {
+      expect(
+        yield* resolveSshRemoteUrl({
+          remoteUrl: "git@work:example/project.git",
+          cwd: "/repo",
+          process: {
+            run: () =>
+              Effect.succeed({
+                stdout: `hostname ${hostname}\r\n`,
+                stderr: "",
+                exitCode: ChildProcessSpawner.ExitCode(0),
+                stdoutTruncated: false,
+                stderrTruncated: false,
+              }),
+          },
+        }),
+      ).toBe(`git@${expectedHost}:example/project.git`);
+    }
+  }),
+);
+
+it.effect("caches SSH hostnames for 30 seconds while preserving each repository path", () =>
+  Effect.gen(function* () {
+    let calls = 0;
+    const process = {
+      run: () =>
+        Effect.sync(() => {
+          calls++;
+          return {
+            stdout: "hostname github.com\n",
+            stderr: "",
+            exitCode: ChildProcessSpawner.ExitCode(0),
+            stdoutTruncated: false,
+            stderrTruncated: false,
+          };
+        }),
+    };
+    const resolve = (remoteUrl: string, cwd = "/repo") =>
+      resolveSshRemoteUrl({ remoteUrl, cwd, process });
+    yield* resolve("git@alias:example/one.git");
+    yield* TestClock.adjust("29 seconds");
+    expect(yield* resolve("git@alias:example/two.git")).toBe("git@github.com:example/two.git");
+    expect(calls).toBe(1);
+    yield* TestClock.adjust("1 second");
+    yield* resolve("git@alias:example/one.git");
+    expect(calls).toBe(2);
+    yield* resolve("git@alias:example/one.git", "/other");
+    yield* resolve("other@alias:example/one.git");
+    yield* resolve("ssh://git@alias:2222/example/one.git");
+    yield* resolve("git@other-alias:example/one.git");
+    expect(calls).toBe(6);
+  }),
+);
+
+it.effect("retries failed SSH lookups immediately", () =>
+  Effect.gen(function* () {
+    for (const failure of ["exit", "missing", "error"] as const) {
+      let calls = 0;
+      const process = {
+        run: () =>
+          Effect.suspend(() => {
+            calls++;
+            if (calls === 1 && failure === "error")
+              return Effect.fail(
+                new VcsProcessTimeoutError({
+                  operation: "test",
+                  command: "ssh",
+                  cwd: "/repo",
+                  timeoutMs: 5000,
+                }),
+              );
+            return Effect.succeed({
+              stdout: calls === 1 && failure === "missing" ? "user git\n" : "hostname github.com\n",
+              stderr: "",
+              exitCode: ChildProcessSpawner.ExitCode(calls === 1 && failure === "exit" ? 1 : 0),
+              stdoutTruncated: false,
+              stderrTruncated: false,
+            });
+          }),
+      };
+      const input = { remoteUrl: "git@alias:example/project.git", cwd: "/repo", process };
+      expect(yield* resolveSshRemoteUrl(input)).toBe(input.remoteUrl);
+      expect(yield* resolveSshRemoteUrl(input)).toBe("git@github.com:example/project.git");
+      expect(calls).toBe(2);
+    }
+  }),
+);
```

## packages/source-control-core/src/server/sshRemote.ts

Status: added; +130; −0

```diff
@@ -1,0 +1,130 @@
+import * as Schema from "effect/Schema";
+import * as Clock from "effect/Clock";
+import * as Effect from "effect/Effect";
+import { isSshRemoteUrl } from "@t3tools/shared/sourceControl";
+import type * as SourceControlHost from "./SourceControlHost.ts";
+
+const SSH_HOSTNAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/u;
+const PUBLIC_HOSTS = new Set([
+  "github.com",
+  "gitlab.com",
+  "bitbucket.org",
+  "codeberg.org",
+  "ssh.dev.azure.com",
+]);
+
+const encodeCacheKey = Schema.encodeSync(Schema.fromJsonString(Schema.Array(Schema.String)));
+const SSH_CACHE_TTL_MS = 30_000;
+const SSH_CACHE_CAPACITY = 256;
+// Process adapters own their environment; never reuse results across adapters.
+const hostnameCaches = new WeakMap<
+  SourceControlHost.SourceControlHost["Service"]["process"],
+  Map<string, { readonly hostname: string; readonly expiresAt: number }>
+>();
+
+/** Keeps the original repository path when replacing the host. */
+function parseSshRemote(remoteUrl: string) {
+  const value = remoteUrl.trim();
+
+  if (value.toLowerCase().startsWith("ssh://")) {
+    const url = URL.parse(value);
+    if (url === null) return null;
+
+    const authorityStart = "ssh://".length;
+    const pathStart = value.indexOf("/", authorityStart);
+    const authority = value.slice(authorityStart, pathStart === -1 ? undefined : pathStart);
+    const hostnameStart = authorityStart + authority.lastIndexOf("@") + 1;
+
+    return {
+      hostname: url.hostname,
+      username: url.username,
+      port: url.port,
+      prefix: value.slice(0, hostnameStart),
+      suffix: value.slice(hostnameStart + url.hostname.length),
+    };
+  }
+
+  const userEnd = value.indexOf("@");
+  const hostnameEnd = value.indexOf(":", userEnd + 1);
+  if (userEnd === -1 || hostnameEnd === -1) return null;
+
+  return {
+    hostname: value.slice(userEnd + 1, hostnameEnd),
+    username: value.slice(0, userEnd),
+    port: "",
+    prefix: value.slice(0, userEnd + 1),
+    suffix: value.slice(hostnameEnd),
+  };
+}
+
+function readSshHostname(stdout: string) {
+  for (const line of stdout.split("\n")) {
+    const [key, value] = line.trim().split(/\s+/u);
+    if (key?.toLowerCase() === "hostname") return value;
+  }
+}
+
+/** Resolves github.com SSH aliases for API lookup. Other hosts keep their existing routing. */
+export const resolveSshRemoteUrl = Effect.fn("resolveSshRemoteUrl")(function* (input: {
+  readonly remoteUrl: string;
+  readonly cwd: string;
+  readonly process: SourceControlHost.SourceControlHost["Service"]["process"];
+}) {
+  if (!isSshRemoteUrl(input.remoteUrl)) return input.remoteUrl;
+
+  const remote = parseSshRemote(input.remoteUrl);
+  // Accept host names and aliases. Reject values that SSH can treat as command options.
+  if (remote === null || !SSH_HOSTNAME_PATTERN.test(remote.hostname)) return input.remoteUrl;
+  // A public API host can use a different SSH endpoint, such as GitHub's port-443 host.
+  if (PUBLIC_HOSTS.has(remote.hostname.toLowerCase())) return input.remoteUrl;
+
+  let cache = hostnameCaches.get(input.process);
+  if (cache === undefined) {
+    cache = new Map();
+    hostnameCaches.set(input.process, cache);
+  }
+  const key = encodeCacheKey([input.cwd, remote.username, remote.port, remote.hostname]);
+  const now = yield* Clock.currentTimeMillis;
+  for (const [entryKey, entry] of cache) {
+    if (entry.expiresAt <= now) cache.delete(entryKey);
+  }
+  const cached = cache.get(key);
+  if (cached !== undefined) {
+    return cached.hostname === "github.com" || cached.hostname === "ssh.github.com"
+      ? `${remote.prefix}github.com${remote.suffix}`
+      : input.remoteUrl;
+  }
+
+  const output = yield* input.process
+    .run({
+      operation: "source-control.resolve-ssh-host",
+      command: "ssh",
+      args: [
+        "-G",
+        ...(remote.username ? ["-l", remote.username] : []),
+        ...(remote.port ? ["-p", remote.port] : []),
+        remote.hostname,
+      ],
+      cwd: input.cwd,
+      timeoutMs: 5_000,
+      maxOutputBytes: 64_000,
+    })
+    .pipe(Effect.orElseSucceed(() => null));
+
+  const resolvedHostname =
+    output?.exitCode === 0 ? readSshHostname(output.stdout)?.toLowerCase() : undefined;
+  if (resolvedHostname !== undefined && SSH_HOSTNAME_PATTERN.test(resolvedHostname)) {
+    if (cache.size >= SSH_CACHE_CAPACITY) {
+      const oldest = cache.keys().next().value;
+      if (oldest !== undefined) cache.delete(oldest);
+    }
+    cache.set(key, {
+      hostname: resolvedHostname,
+      expiresAt: (yield* Clock.currentTimeMillis) + SSH_CACHE_TTL_MS,
+    });
+  }
+  if (resolvedHostname !== "github.com" && resolvedHostname !== "ssh.github.com")
+    return input.remoteUrl;
+
+  return `${remote.prefix}github.com${remote.suffix}`;
+});
```

## packages/source-control-github/src/server/GitHubCredentials.test.ts

Status: modified; +109; −1

```diff
@@ -29,10 +29,25 @@
   hosts: Record<string, { readonly account?: string; readonly enabled?: boolean }> = {},
   signedOut: ReadonlyArray<string> = [],
   tokens: Record<string, string> = {},
+  repositories: Record<string, string | number> = {},
 ) {
   const calls: Array<ReadonlyArray<string>> = [];
+  const gitCalls: string[] = [];
   const run: SourceControlHost.SourceControlHost["Service"]["process"]["run"] = (input) =>
     Effect.sync(() => {
+      if (input.command === "git") {
+        gitCalls.push(input.cwd);
+        const value = repositories[input.cwd];
+        return {
+          exitCode: ChildProcessSpawner.ExitCode(
+            typeof value === "number" ? value : value === undefined ? 1 : 0,
+          ),
+          stdout: typeof value === "string" ? value : "",
+          stderr: "",
+          stdoutTruncated: false,
+          stderrTruncated: false,
+        };
+      }
       calls.push(input.args);
       const user = input.args[input.args.indexOf("--user") + 1];
       if (input.args.includes("--user") && user !== undefined && signedOut.includes(user)) {
@@ -63,7 +78,7 @@
     ),
     Layer.provide(NodeCrypto.layer),
   );
-  return { layer, calls };
+  return { layer, calls, gitCalls };
 }
 
 describe("GitHubCredentials", () => {
@@ -149,3 +164,96 @@
     }).pipe(Effect.provide(layer));
   });
 });
+
+it.effect(
+  "keeps repo accounts separate from settings and each other, including invalidation",
+  () => {
+    const { layer, calls } = harness(
+      { "github.com": { account: "personal" } },
+      [],
+      {},
+      {
+        "/work": "work-user",
+        "/other": "other-user",
+      },
+    );
+    return Effect.gen(function* () {
+      const credentials = yield* GitHubCredentials.GitHubCredentials;
+      const get = (cwd: string) =>
+        credentials
+          .get("github.com")
+          .pipe(Effect.provideService(GitHubCredentials.RepositoryDirectory, cwd));
+      expect(Redacted.value((yield* get("/work")).token)).toBe("token-for-work-user");
+      expect(Redacted.value((yield* get("/other")).token)).toBe("token-for-other-user");
+      yield* credentials
+        .invalidate("github.com")
+        .pipe(Effect.provideService(GitHubCredentials.RepositoryDirectory, "/work"));
+      yield* get("/work");
+      yield* get("/other");
+      expect(calls).toEqual([
+        ["auth", "token", "--hostname", "github.com", "--user", "work-user"],
+        ["auth", "token", "--hostname", "github.com", "--user", "other-user"],
+        ["auth", "token", "--hostname", "github.com", "--user", "work-user"],
+      ]);
+    }).pipe(Effect.provide(layer));
+  },
+);
+
+it.effect("does not fall back to another account when the repo account is signed out", () => {
+  const { layer, calls } = harness({}, ["work-user"], {}, { "/work": "work-user" });
+  return Effect.gen(function* () {
+    const credentials = yield* GitHubCredentials.GitHubCredentials;
+    const error = yield* Effect.flip(
+      credentials
+        .get("github.com")
+        .pipe(Effect.provideService(GitHubCredentials.RepositoryDirectory, "/work")),
+    );
+    expect(error._tag).toBe("GitHubNotSignedInError");
+    expect(calls).toEqual([["auth", "token", "--hostname", "github.com", "--user", "work-user"]]);
+  }).pipe(Effect.provide(layer));
+});
+
+it.effect("reuses the repo account lookup for repeated API reads", () => {
+  const { layer, gitCalls } = harness({}, [], {}, { "/repo": "work" });
+  return Effect.gen(function* () {
+    const credentials = yield* GitHubCredentials.GitHubCredentials;
+    for (let i = 0; i < 5; i++)
+      yield* credentials
+        .get("github.com")
+        .pipe(Effect.provideService(GitHubCredentials.RepositoryDirectory, "/repo"));
+    expect(gitCalls).toEqual(["/repo"]);
+  }).pipe(Effect.provide(layer));
+});
+
+it.effect("keeps GitHub.com repo accounts out of Enterprise requests", () => {
+  const { layer, calls, gitCalls } = harness(
+    { "ghe.example.test": { account: "enterprise" } },
+    [],
+    {},
+    { "/repo": "personal" },
+  );
+  return Effect.gen(function* () {
+    const credentials = yield* GitHubCredentials.GitHubCredentials;
+    yield* credentials
+      .get("ghe.example.test")
+      .pipe(Effect.provideService(GitHubCredentials.RepositoryDirectory, "/repo"));
+    expect(calls).toEqual([
+      ["auth", "token", "--hostname", "ghe.example.test", "--user", "enterprise"],
+    ]);
+    expect(gitCalls).toEqual([]);
+  }).pipe(Effect.provide(layer));
+});
+
+it.effect("fails unreadable repo configuration instead of selecting another account", () => {
+  const { layer, calls } = harness({}, [], {}, { "/repo": 128 });
+  return Effect.gen(function* () {
+    const credentials = yield* GitHubCredentials.GitHubCredentials;
+    const error = yield* Effect.flip(
+      credentials
+        .get("github.com")
+        .pipe(Effect.provideService(GitHubCredentials.RepositoryDirectory, "/repo")),
+    );
+    expect(error._tag).toBe("GitHubRepositoryAccountError");
+    expect(calls).toEqual([]);
+  }).pipe(Effect.provide(layer));
+});
```

## packages/source-control-github/src/server/GitHubCredentials.ts

Status: modified; +81; −11

```diff
@@ -48,7 +48,7 @@
   override get message(): string {
     return this.account === undefined
       ? `No GitHub credential for ${this.host}: run \`gh auth login --hostname ${this.host}\`.`
-      : `No GitHub credential for ${this.account} on ${this.host}: run \`gh auth login --hostname ${this.host}\` for that account or pick another in Settings → Source Control.`;
+      : `No GitHub credential for ${this.account} on ${this.host}: run \`gh auth login --hostname ${this.host}\` for that account, or review Settings → Source Control and \`git config github.user\`.`;
   }
 }
 
@@ -74,7 +74,24 @@
   | GitHubCliMissingError
   | GitHubNotSignedInError
   | GitHubHostDisabledError
+  | GitHubCliFailedError
+  | GitHubRepositoryAccountError;
+
+/** The account choice could not be read, so another login must not be used. */
+export class GitHubRepositoryAccountError extends Schema.TaggedError<GitHubRepositoryAccountError>()(
+  "GitHubRepositoryAccountError",
+  { cwd: Schema.String, cause: Schema.Defect() },
+) {
+  override get message(): string {
+    return `T3 Code cannot read \`git config github.user\` in ${this.cwd}. Run \`git config --get github.user\` in that folder to see the Git error.`;
+  }
+}
+
+/** The checkout for this request. Host discovery does not select a repository account. */
+export const RepositoryDirectory = Context.Reference<string | null>(
+  "@t3tools/source-control-github/server/GitHubCredentials/RepositoryDirectory",
+  { defaultValue: () => null },
+);
-  | GitHubCliFailedError;
 
 export class GitHubCredentials extends Context.Service<
   GitHubCredentials,
@@ -183,18 +200,64 @@
       Effect.orElseSucceed(() => null),
     );
 
-  const cacheKey = (host: string, account: string | undefined) =>
-    account === undefined ? host : `${host}\u0000${account}`;
+  /** Cache key: the host plus its pinned account, so a changed pin misses the cache. */
+  const cacheKey = (host: string, account: string | undefined, repository = false) =>
+    account === undefined ? host : `${host}\u0000${account}${repository ? "\u0000repo" : ""}`;
 
+  const repositoryAccounts = yield* Cache.makeWith(
+    (cwd: string) =>
+      process
+        .run({
+          operation: "GitHubCredentials.repositoryAccount",
+          command: "git",
+          args: ["config", "--get", "github.user"],
+          cwd,
+          allowNonZeroExit: true,
+          timeoutMs: 5_000,
+        })
+        .pipe(
+          Effect.mapError((cause) => new GitHubRepositoryAccountError({ cwd, cause })),
+          Effect.flatMap((output) =>
+            output.exitCode === 0
+              ? Effect.succeed(output.stdout.trim() || undefined)
+              : output.exitCode === 1
+                ? Effect.succeed(undefined)
+                : Effect.fail(
+                    new GitHubRepositoryAccountError({
+                      cwd,
+                      cause: new Error(`Git exited with code ${output.exitCode}.`),
+                    }),
+                  ),
+          ),
+        ),
+    {
+      capacity: 512,
+      timeToLive: (exit) => (Exit.isSuccess(exit) ? Duration.seconds(5) : Duration.zero),
+    },
+  );
+
+  const repositoryAccount = Effect.fn("GitHubCredentials.repositoryAccount")(function* (
+    host: string,
+  ) {
+    const cwd = yield* RepositoryDirectory;
+    if (host !== "github.com" || cwd === null || environmentToken(host, environment) !== null)
+      return undefined;
+    return yield* Cache.get(repositoryAccounts, cwd);
+  });
+
   const lookup = Effect.fn("GitHubCredentials.lookup")(function* (key: string) {
-    const [host = key, choice] = key.split("\u0000");
+    const [host = key, choice, repository] = key.split("\u0000");
+    // An environment token wins over a pinned account, exactly as it does in gh.
     const fromEnv = environmentToken(host, environment);
+    // Keep the existing Settings fallback. A repository account must never fall back to another login.
     const token =
       fromEnv ??
       (yield* fromGh(host, choice).pipe(
         Effect.catchTags({
           GitHubNotSignedInError: (error) =>
-            choice === undefined ? Effect.fail(error) : fromGh(host, undefined),
+            choice === undefined || repository === "repo"
+              ? Effect.fail(error)
+              : fromGh(host, undefined),
         }),
       ));
     return {
@@ -233,14 +296,21 @@
           fingerprint: yield* fingerprintOf(host, saved),
         } satisfies GitHubCredential;
       }
+      const account = yield* repositoryAccount(host);
+      return yield* Cache.get(
-      return yield* Cache.get(cache, cacheKey(host, choice?.account));
+        cache,
+        cacheKey(host, account ?? choice?.account, account !== undefined),
+      );
     }),
-    invalidate: (rawHost) => {
+    invalidate: Effect.fn("GitHubCredentials.invalidate")(function* (rawHost) {
       const host = normalizeHost(rawHost);
+      const choice = yield* hostChoice(host);
+      const account = yield* repositoryAccount(host).pipe(Effect.orElseSucceed(() => undefined));
+      yield* Cache.invalidate(
+        cache,
-      return hostChoice(host).pipe(
+        cacheKey(host, account ?? choice?.account, account !== undefined),
-        Effect.flatMap((choice) => Cache.invalidate(cache, cacheKey(host, choice?.account))),
       );
-    },
+    }),
   });
 });
 
```

## packages/source-control-github/src/server/GitHubPullRequestApi.test.ts

Status: modified; +165; −0

```diff
@@ -1,5 +1,6 @@
 import * as NodeServices from "@effect/platform-node/NodeServices";
 import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
+import { SourceControlProviderKind } from "@t3tools/contracts";
 import { afterEach, assert, expect, it, vi } from "@effect/vitest";
 import * as Effect from "effect/Effect";
 import * as Deferred from "effect/Deferred";
@@ -3787,3 +3788,167 @@
     }),
   );
 });
+
+it.effect("loads PR details and files with the repo account rather than the active account", () =>
+  Effect.gen(function* () {
+    const credentials = Layer.succeed(
+      GitHubCredentials.GitHubCredentials,
+      GitHubCredentials.GitHubCredentials.of({
+        get: (host) =>
+          Effect.gen(function* () {
+            const cwd = yield* GitHubCredentials.RepositoryDirectory;
+            const account = cwd === "/work repo" ? "work" : "personal";
+            return {
+              host,
+              token: Redacted.make(`fake-${account}`),
+              source: "gh" as const,
+              fingerprint: `${host}:${account}`,
+            };
+          }),
+        invalidate: () => Effect.void,
+      }),
+    );
+    const http = Layer.succeed(
+      HttpClient.HttpClient,
+      HttpClient.make((request) =>
+        Effect.succeed(
+          HttpClientResponse.fromWeb(
+            request,
+            request.headers.authorization !== "Bearer fake-work"
+              ? new Response(encodeJson({ message: "Not Found" }), { status: 404 })
+              : new Response(
+                  encodeJson(request.url.endsWith("/graphql") ? coreResponse({ id: "PR_7" }) : []),
+                  { headers: { "content-type": "application/json" } },
+                ),
+          ),
+        ),
+      ),
+    );
+    const cli = yield* GitHubPullRequestApi.make.pipe(
+      Effect.provide(
+        GitHubRest.layer.pipe(
+          Layer.provideMerge(GitHubRequestPolicy.layer),
+          Layer.provideMerge(
+            Layer.mergeAll(credentials, http, GitHubQuota.layer, SourceControlRateLimit.layer),
+          ),
+          Layer.provideMerge(TestSourceControlHost.layer()),
+          Layer.provideMerge(NodeServices.layer),
+        ),
+      ),
+    );
+    const input = { cwd: "/work repo", repository: "acme/web", host: "github.com", number: 7 };
+    expect((yield* cli.getPullRequestDetail(input)).title).toBe("Pull request 7");
+    expect((yield* cli.getPullRequestDiff({ ...input, cursor: "1" })).patch).toBe("");
+    expect(
+      (yield* Effect.flip(cli.runPullRequestAction({ ...input, action: "merge", stackNumber: 1 })))
+        ._tag,
+    ).toBe("GitHubStackChangedError");
+    expect(
+      (yield* Effect.flip(cli.getPullRequestDetail({ ...input, cwd: "/personal repo" })))._tag,
+    ).toBe("GitHubApiNotFoundError");
+  }),
+);
+
+layer("repo account batching", (it) => {
+  it.effect("keeps summaries from different checkouts under their own account", () =>
+    Effect.gen(function* () {
+      const directories: Array<string | null> = [];
+      mockedCredential.mockImplementation((host) =>
+        Effect.gen(function* () {
+          const cwd = yield* GitHubCredentials.RepositoryDirectory;
+          return { token: Redacted.make("fake-token"), fingerprint: `${host}:${cwd}` };
+        }),
+      );
+      mockedExecute.mockImplementation(() =>
+        Effect.gen(function* () {
+          const cwd = yield* GitHubCredentials.RepositoryDirectory;
+          directories.push(cwd);
+          const pullRequest = {
+            ...coreResponse().data.repository.pullRequest,
+            title: cwd,
+            latestReviews: { nodes: [] },
+          };
+          return output(encodeJson({ data: { _0_repository: { pullRequest } } }));
+        }),
+      );
+      const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi;
+      const pending = yield* Effect.forEach(
+        ["/work repo", "/personal repo"],
+        (cwd) =>
+          cli.getPullRequestSummary({ cwd, repository: "acme/web", host: "github.com", number: 7 }),
+        { concurrency: "unbounded" },
+      ).pipe(Effect.forkChild);
+      yield* TestClock.adjust("10 millis");
+      expect((yield* Fiber.join(pending)).map((summary) => summary.title)).toEqual([
+        "/work repo",
+        "/personal repo",
+      ]);
+      expect(directories.toSorted()).toEqual(["/personal repo", "/work repo"]);
+    }),
+  );
+});
+
+it.effect(
+  "holds a background account lookup during a pause while allowing an interactive lookup",
+  () => {
+    let requests = 0;
+    const dependencies = Layer.mergeAll(
+      Layer.succeed(
+        GitHubCredentials.GitHubCredentials,
+        GitHubCredentials.GitHubCredentials.of({
+          get: (host) =>
+            Effect.succeed({
+              host,
+              token: Redacted.make("fake-credential"),
+              fingerprint: "test-account",
+              source: "gh" as const,
+            }),
+          invalidate: () => Effect.void,
+        }),
+      ),
+      Layer.succeed(
+        HttpClient.HttpClient,
+        HttpClient.make((request) => {
+          requests += 1;
+          return Effect.succeed(
+            HttpClientResponse.fromWeb(
+              request,
+              new Response(encodeJson({ id: 123, login: "work-user" })),
+            ),
+          );
+        }),
+      ),
+      GitHubQuota.layer,
+      SourceControlRateLimit.layer,
+      TestSourceControlHost.layer(),
+      NodeServices.layer,
+    );
+    return Effect.gen(function* () {
+      const policy = yield* GitHubRequestPolicy.make;
+      const api = yield* GitHubRest.make.pipe(
+        Effect.provideService(GitHubRequestPolicy.GitHubRequestPolicy, policy),
+      );
+      const cli = yield* GitHubPullRequestApi.make.pipe(
+        Effect.provideService(GitHubRest.GitHubRest, api),
+        Effect.provideService(GitHubRequestPolicy.GitHubRequestPolicy, policy),
+      );
+      const limits = yield* SourceControlRateLimit.SourceControlRateLimit;
+      const key = { provider: SourceControlProviderKind.make("github"), host: "github.com" };
+      yield* limits
+        .recordRateLimit({ ...key, lease: 0 })
+        .pipe(Effect.provideService(SourceControlRateLimit.CredentialScope, "test-account"));
+      const input = { cwd: "/work", host: "github.com" };
+      const refused = yield* Effect.flip(
+        cli.withVerifiedCredential({ ...input, allowReserve: false }, Effect.succeed),
+      );
+      expect(refused._tag).toBe("SourceControlRateLimitPausedError");
+      expect(requests).toBe(0);
+      const identity = yield* cli.withVerifiedCredential(
+        { ...input, allowReserve: true },
+        Effect.succeed,
+      );
+      expect(identity.viewer).toBe("work-user");
+      expect(requests).toBe(1);
+    }).pipe(Effect.provide(dependencies));
+  },
+);
```

## packages/source-control-github/src/server/GitHubPullRequestApi.ts

Status: modified; +198; −186

```diff
@@ -44,6 +44,7 @@
   type PullRequestPreview,
 } from "@t3tools/contracts";
 
+import * as GitHubCredentials from "./GitHubCredentials.ts";
 import * as GitHubRest from "./GitHubRest.ts";
 import * as GitHubRequestPolicy from "./GitHubRequestPolicy.ts";
 import * as GitHubGraphQlClient from "./gitHubGraphQlClient.ts";
@@ -426,7 +427,7 @@
   GitHubPullRequestApi,
   {
     readonly withVerifiedCredential: <A, E, R>(
+      input: { readonly cwd: string; readonly host: string; readonly allowReserve?: boolean },
-      input: { readonly cwd: string; readonly host: string },
       use: (identity: {
         readonly accountId: string;
         readonly viewer: string;
@@ -947,6 +948,10 @@
   const client = yield* GraphQLClient.make(PullRequestGraphQLGroup).pipe(
     Effect.provide(GitHubGraphQlClient.layer),
   );
+  const inRepository = (cwd: string) =>
+    Effect.provideService(GitHubCredentials.RepositoryDirectory, cwd);
+  const rest = (input: GitHubRest.GitHubRestInput & { readonly cwd: string }) =>
+    api.rest(input).pipe(inRepository(input.cwd));
   const routingIdentities = new Map<
     string,
     {
@@ -962,10 +967,14 @@
   const decodeRoutingIdentity = Schema.decodeUnknownEffect(Schema.fromJsonString(RoutingIdentity));
   const decodeViewerIdentity = Schema.decodeUnknownEffect(RoutingIdentity);
   const captureVerifiedCredential = Effect.fn("GitHubPullRequestApi.captureVerifiedCredential")(
+    function* (input: {
-    function* (input: { readonly cwd: string; readonly host: string }) {
+      readonly cwd: string;
+      readonly host: string;
+      readonly allowReserve?: boolean;
+    }) {
       const unavailable = () => new GitHubViewerLoginUnavailableError({ cwd: input.cwd });
       const host = input.host.toLowerCase();
+      const { token, fingerprint: key } = yield* api.credential(host).pipe(inRepository(input.cwd));
-      const { token, fingerprint: key } = yield* api.credential(host);
       const credential = { host, token, credentialFingerprint: key };
       return yield* Effect.acquireUseRelease(
         Effect.sync(() => {
@@ -982,35 +991,39 @@
               if (cached !== undefined && now - cached.at < 10 * 60_000)
                 return { ...credential, ...cached.value };
               // Pin verification so an auth switch cannot poison its cache entry.
-              const identity = yield* api
-                .rest({ host, operation: "getRoutingIdentity", path: "user", allowReserve: true })
-                .pipe(
-                  Effect.flatMap((response) =>
-                    decodeRoutingIdentity(response.body).pipe(Effect.mapError(unavailable)),
-                  ),
-                  Effect.catchTags({
-                    GitHubApiResponseError: (error) =>
-                      Effect.gen(function* () {
-                        if (error.status !== 403) return yield* error;
-                        const { viewer } = yield* client.RoutingIdentityViewer({}).pipe(
-                          GitHubGraphQlClient.call({
-                            host,
-                            operation: "getRoutingIdentity",
-                            allowReserve: true,
-                          }),
-                          Effect.catchTags({
-                            GraphQLClientError: () => Effect.fail(unavailable()),
-                          }),
-                        );
-                        return yield* decodeViewerIdentity({
-                          id: viewer.databaseId,
-                          login: viewer.login,
-                        }).pipe(Effect.mapError(unavailable));
-                      }),
-                  }),
-                  Effect.provideService(GitHubRequestPolicy.PinnedGitHubCredential, credential),
-                  Effect.provideService(SourceControlRateLimit.CredentialScope, key),
+              const identity = yield* rest({
-                );
+                cwd: input.cwd,
+                host,
+                operation: "getRoutingIdentity",
+                path: "user",
+                allowReserve: input.allowReserve ?? true,
+              }).pipe(
+                Effect.flatMap((response) =>
+                  decodeRoutingIdentity(response.body).pipe(Effect.mapError(unavailable)),
+                ),
+                Effect.catchTags({
+                  GitHubApiResponseError: (error) =>
+                    Effect.gen(function* () {
+                      if (error.status !== 403) return yield* error;
+                      const { viewer } = yield* client.RoutingIdentityViewer({}).pipe(
+                        GitHubGraphQlClient.call({
+                          host,
+                          operation: "getRoutingIdentity",
+                          allowReserve: input.allowReserve ?? true,
+                        }),
+                        Effect.catchTags({
+                          GraphQLClientError: () => Effect.fail(unavailable()),
+                        }),
+                      );
+                      return yield* decodeViewerIdentity({
+                        id: viewer.databaseId,
+                        login: viewer.login,
+                      }).pipe(Effect.mapError(unavailable));
+                    }),
+                }),
+                Effect.provideService(GitHubRequestPolicy.PinnedGitHubCredential, credential),
+                Effect.provideService(SourceControlRateLimit.CredentialScope, key),
+              );
               const value = { accountId: String(identity.id), viewer: identity.login };
               if (routingIdentities.size >= 128)
                 routingIdentities.delete(routingIdentities.keys().next().value!);
@@ -1033,6 +1046,7 @@
     captureVerifiedCredential(input).pipe(
       Effect.flatMap(({ host, token, accountId, viewer, credentialFingerprint }) =>
         use({ accountId, viewer, credentialFingerprint }).pipe(
+          inRepository(input.cwd),
           Effect.provideService(SourceControlRateLimit.CredentialScope, credentialFingerprint),
           Effect.provideService(GitHubRequestPolicy.PinnedGitHubCredential, {
             host,
@@ -1073,6 +1087,7 @@
       Exclude<R, GitHubGraphQlClient.GitHubGraphQlTarget>
     > =>
       effect.pipe(
+        inRepository(input.cwd),
         GitHubGraphQlClient.call({
           host: input.host,
           operation: input.operation,
@@ -1213,13 +1228,11 @@
 
   /** A REST read whose JSON answer is decoded the same way. */
   const restRead = <A>(input: RestReadInput<A>): Effect.Effect<A, GitHubPullRequestApiError> =>
-    api
-      .rest({ host: input.host, operation: input.operation, path: input.path })
-      .pipe(
-        Effect.flatMap((response) =>
-          decodeWith(input.cwd, input.operation, input.decode, response.body),
-        ),
+    rest({ cwd: input.cwd, host: input.host, operation: input.operation, path: input.path }).pipe(
-      );
+      Effect.flatMap((response) =>
+        decodeWith(input.cwd, input.operation, input.decode, response.body),
+      ),
+    );
 
   // The last ETag and body of each polled REST read, so an unchanged answer comes back as a
   // 304, which GitHub does not charge. Keyed by the credential the request is pinned to, so a
@@ -1235,23 +1248,22 @@
     input: RestReadInput<A>,
   ) {
     const host = GitHubRequestPolicy.normalizeHost(input.host);
+    const { token, fingerprint } = yield* api.credential(host).pipe(inRepository(input.cwd));
-    const { token, fingerprint } = yield* api.credential(host);
     const key = `${fingerprint}\0${host}\0${input.path}`;
     const previous = yield* Cache.get(validators, key);
-    const response = yield* api
+    const response = yield* rest({
-      .rest({
+      cwd: input.cwd,
+      host,
+      operation: input.operation,
+      path: input.path,
+      ...(Option.isSome(previous) ? { ifNoneMatch: previous.value.etag } : {}),
+    }).pipe(
+      Effect.provideService(GitHubRequestPolicy.PinnedGitHubCredential, {
         host,
-        operation: input.operation,
-        path: input.path,
-        ...(Option.isSome(previous) ? { ifNoneMatch: previous.value.etag } : {}),
-      })
-      .pipe(
-        Effect.provideService(GitHubRequestPolicy.PinnedGitHubCredential, {
-          host,
-          token,
-          credentialFingerprint: fingerprint,
-        }),
-      );
+        token,
+        credentialFingerprint: fingerprint,
+      }),
+    );
     if (response.status === 304 && Option.isSome(previous)) {
       yield* Cache.set(validators, key, previous);
       return yield* decodeWith(input.cwd, input.operation, input.decode, previous.value.body);
@@ -1274,51 +1286,50 @@
   }): Effect.Effect<GitHubPullRequestDiffSlice, GitHubPullRequestApiError> => {
     const { owner, name } = parseRepositorySelector(input.repository);
     const paging = `per_page=${DIFF_FILES_PAGE_SIZE}&page=${input.page}`;
-    return api
-      .rest({
-        host: input.host,
-        operation: "getPullRequestDiff",
-        path:
-          input.commit === undefined
-            ? `repos/${owner}/${name}/pulls/${input.number}/files?${paging}`
-            : `repos/${owner}/${name}/commits/${input.commit}?${paging}`,
-        maxResponseBytes: DIFF_MAX_OUTPUT_BYTES,
-        timeout: DIFF_TIMEOUT,
-      })
-      .pipe(
-        Effect.flatMap((response) => {
-          // Checked before decoding: a byte-truncated response is a JSON prefix, which would
-          // fail to parse.
-          if (response.truncated) {
-            return Effect.fail(
-              readError(
-                input.cwd,
-                "getPullRequestDiff",
-                new Error(`Page ${input.page} of the changed files was too large to read.`),
-              ),
-            );
-          }
-          return decodeWith(
-            input.cwd,
-            "getPullRequestDiff",
-            input.commit === undefined ? decodePullRequestFilesJson : decodeCommitFilesJson,
-            response.body,
+    return rest({
+      cwd: input.cwd,
+      host: input.host,
+      operation: "getPullRequestDiff",
+      path:
+        input.commit === undefined
+          ? `repos/${owner}/${name}/pulls/${input.number}/files?${paging}`
+          : `repos/${owner}/${name}/commits/${input.commit}?${paging}`,
+      maxResponseBytes: DIFF_MAX_OUTPUT_BYTES,
+      timeout: DIFF_TIMEOUT,
+    }).pipe(
+      Effect.flatMap((response) => {
+        // Checked before decoding: a byte-truncated response is a JSON prefix, which would
+        // fail to parse.
+        if (response.truncated) {
+          return Effect.fail(
+            readError(
+              input.cwd,
+              "getPullRequestDiff",
+              new Error(`Page ${input.page} of the changed files was too large to read.`),
+            ),
           );
-        }),
-        Effect.map((files) => {
-          // Counted before decoding, so a page whose files all failed to decode still moves on
-          // rather than pointing the reader back at the page it just read.
-          const morePages = files.rawCount >= DIFF_FILES_PAGE_SIZE;
-          return {
-            patch: files.patch,
-            truncated: files.truncated,
-            nextCursor: morePages ? String(input.page + 1) : null,
-            ...(files.omittedFileStats.length === 0
-              ? {}
-              : { omittedFileStats: files.omittedFileStats }),
-          };
-        }),
-      );
+        }
+        return decodeWith(
+          input.cwd,
+          "getPullRequestDiff",
+          input.commit === undefined ? decodePullRequestFilesJson : decodeCommitFilesJson,
+          response.body,
+        );
+      }),
+      Effect.map((files) => {
+        // Counted before decoding, so a page whose files all failed to decode still moves on
+        // rather than pointing the reader back at the page it just read.
+        const morePages = files.rawCount >= DIFF_FILES_PAGE_SIZE;
+        return {
+          patch: files.patch,
+          truncated: files.truncated,
+          nextCursor: morePages ? String(input.page + 1) : null,
+          ...(files.omittedFileStats.length === 0
+            ? {}
+            : { omittedFileStats: files.omittedFileStats }),
+        };
+      }),
+    );
   };
 
   const getPullRequestDiffFileContents: GitHubPullRequestApi["Service"]["getPullRequestDiffFileContents"] =
@@ -1327,7 +1338,8 @@
         return yield* new GitHubDiffCommitError({ cwd: input.cwd });
       }
       const { owner, name } = parseRepositorySelector(input.repository);
+      const refsResponse = yield* rest({
-      const refsResponse = yield* api.rest({
+        cwd: input.cwd,
         host: input.host,
         operation: "getPullRequestDiffFileContents",
         path:
@@ -1362,31 +1374,30 @@
       }
 
       const readFile = (revision: string, filePath: string) =>
-        api
-          .rest({
-            host: input.host,
-            operation: "getPullRequestDiffFileContents",
-            accept: "application/vnd.github.raw+json",
-            path: `repos/${owner}/${name}/contents/${filePath
-              .split("/")
-              .map(encodeURIComponent)
-              .join("/")}?ref=${encodeURIComponent(revision)}`,
-            maxResponseBytes: DIFF_FILE_MAX_OUTPUT_BYTES,
-            timeout: DIFF_TIMEOUT,
-          })
-          .pipe(
-            Effect.flatMap((response) =>
-              response.truncated || response.body.includes("\0") || response.invalidUtf8
-                ? Effect.fail(
-                    new GitHubDiffFileContentsUnavailableError({
-                      cwd: input.cwd,
-                      path: filePath,
-                      reason: response.truncated ? "oversized" : "binary",
-                    }),
-                  )
-                : Effect.succeed(response.body),
-            ),
+        rest({
-          );
+          cwd: input.cwd,
+          host: input.host,
+          operation: "getPullRequestDiffFileContents",
+          accept: "application/vnd.github.raw+json",
+          path: `repos/${owner}/${name}/contents/${filePath
+            .split("/")
+            .map(encodeURIComponent)
+            .join("/")}?ref=${encodeURIComponent(revision)}`,
+          maxResponseBytes: DIFF_FILE_MAX_OUTPUT_BYTES,
+          timeout: DIFF_TIMEOUT,
+        }).pipe(
+          Effect.flatMap((response) =>
+            response.truncated || response.body.includes("\0") || response.invalidUtf8
+              ? Effect.fail(
+                  new GitHubDiffFileContentsUnavailableError({
+                    cwd: input.cwd,
+                    path: filePath,
+                    reason: response.truncated ? "oversized" : "binary",
+                  }),
+                )
+              : Effect.succeed(response.body),
+          ),
+        );
 
       const [oldContents, newContents] = yield* Effect.all(
         [
@@ -1671,7 +1682,7 @@
     A,
     | Exclude<E, GraphQLClientError.GraphQLClientError>
     | GitHubPullRequestReadError
-    | GitHubRequestPolicy.GitHubApiNotFoundError,
+    | GitHubRequestPolicy.GitHubApiError,
     Exclude<R, GitHubGraphQlClient.GitHubGraphQlTarget>
   > =>
     effect.pipe(
@@ -1679,9 +1690,13 @@
         host: input.host,
         operation: input.operation,
       }),
+      GitHubRequestPolicy.withCredential(policy, input.host),
+      inRepository(input.cwd),
       Effect.mapError((error) =>
         !isGraphQlClientError(error)
+          ? (error as
+              | Exclude<E, GraphQLClientError.GraphQLClientError>
-          ? (error as Exclude<E, GraphQLClientError.GraphQLClientError>)
+              | GitHubRequestPolicy.GitHubApiError)
           : isNotFoundOnly(error)
             ? new GitHubRequestPolicy.GitHubApiNotFoundError({
                 host: input.host,
@@ -2132,27 +2147,26 @@
         return filesPage(1);
       }
       const { owner, name } = parseRepositorySelector(input.repository);
-      return api
-        .rest({
-          host: input.host,
-          operation: "getPullRequestDiff",
-          path: `repos/${owner}/${name}/pulls/${input.number}`,
-          accept: "application/vnd.github.diff",
-          maxResponseBytes: DIFF_MAX_OUTPUT_BYTES,
-          timeout: DIFF_TIMEOUT,
-        })
-        .pipe(
-          Effect.flatMap((response) =>
-            response.truncated
-              ? filesPage(1)
-              : Effect.succeed({ patch: response.body, truncated: false, nextCursor: null }),
-          ),
-          // GitHub answers 406 rather than a diff past 300 changed files, so the patch is read
-          // from the files API instead, a page per call.
-          Effect.catchTags({
-            GitHubApiResponseError: (error) => filesPage(1).pipe(Effect.mapError(() => error)),
-          }),
+      return rest({
+        cwd: input.cwd,
-        );
+        host: input.host,
+        operation: "getPullRequestDiff",
+        path: `repos/${owner}/${name}/pulls/${input.number}`,
+        accept: "application/vnd.github.diff",
+        maxResponseBytes: DIFF_MAX_OUTPUT_BYTES,
+        timeout: DIFF_TIMEOUT,
+      }).pipe(
+        Effect.flatMap((response) =>
+          response.truncated
+            ? filesPage(1)
+            : Effect.succeed({ patch: response.body, truncated: false, nextCursor: null }),
+        ),
+        // GitHub answers 406 rather than a diff past 300 changed files, so the patch is read
+        // from the files API instead, a page per call.
+        Effect.catchTags({
+          GitHubApiResponseError: (error) => filesPage(1).pipe(Effect.mapError(() => error)),
+        }),
+      );
     },
 
     getPullRequestDiffFileContents,
@@ -2334,15 +2348,14 @@
 
     setReviewerRequest: (input) => {
       const { owner, name } = parseRepositorySelector(input.repository);
-      return api
-        .rest({
-          host: input.host,
-          operation: "setReviewerRequest",
-          method: input.requested ? "POST" : "DELETE",
-          path: `repos/${owner}/${name}/pulls/${input.number}/requested_reviewers`,
-          body: buildReviewerRequest(input.reviewers),
-        })
-        .pipe(Effect.asVoid);
+      return rest({
+        cwd: input.cwd,
+        host: input.host,
+        operation: "setReviewerRequest",
+        method: input.requested ? "POST" : "DELETE",
+        path: `repos/${owner}/${name}/pulls/${input.number}/requested_reviewers`,
+        body: buildReviewerRequest(input.reviewers),
+      }).pipe(Effect.asVoid);
     },
 
     listLabelCandidates: (input) => {
@@ -2385,20 +2398,20 @@
       const { owner, name } = parseRepositorySelector(input.repository);
       const issue = `repos/${owner}/${name}/issues/${input.number}/labels`;
       if (input.applied) {
-        return api
-          .rest({
-            host: input.host,
-            operation: "setLabels",
-            method: "POST",
-            path: issue,
-            body: buildLabelRequest(input.labels),
-          })
+        return rest({
-          .pipe(Effect.asVoid);
+          cwd: input.cwd,
+          host: input.host,
+          operation: "setLabels",
+          method: "POST",
+          path: issue,
+          body: buildLabelRequest(input.labels),
+        }).pipe(Effect.asVoid);
       }
       return Effect.forEach(
         input.labels,
         (label) =>
+          rest({
-          api.rest({
+            cwd: input.cwd,
             host: input.host,
             operation: "setLabels",
             method: "DELETE",
@@ -2491,6 +2504,7 @@
       }
       if (input.stackNumber !== undefined)
         return runGitHubStackAction({ ...input, stackNumber: input.stackNumber }).pipe(
+          inRepository(input.cwd),
           Effect.provideService(GitHubRest.GitHubRest, api),
           Effect.provideService(GitHubRequestPolicy.GitHubRequestPolicy, policy),
           Effect.provideService(HttpClient.HttpClient, httpClient),
@@ -2568,14 +2582,13 @@
                       }),
                       Effect.flatMap((currentRuns) =>
                         currentRuns.some((current) => current.id === run.id)
-                          ? api
-                              .rest({
-                                host: input.host,
-                                operation: "approveWorkflowRun",
-                                method: "POST",
-                                path: `repos/${owner}/${name}/actions/runs/${run.id}/approve`,
-                              })
+                          ? rest({
+                              cwd: input.cwd,
-                              .pipe(Effect.asVoid)
+                              host: input.host,
+                              operation: "approveWorkflowRun",
+                              method: "POST",
+                              path: `repos/${owner}/${name}/actions/runs/${run.id}/approve`,
+                            }).pipe(Effect.asVoid)
                           : Effect.void,
                       ),
                     ),
@@ -2679,19 +2692,18 @@
 
     submitReview: (input) => {
       const { owner, name } = parseRepositorySelector(input.repository);
-      return api
-        .rest({
-          host: input.host,
-          operation: "submitReview",
-          method: "POST",
-          path: `repos/${owner}/${name}/pulls/${input.number}/reviews`,
-          body: buildReviewSubmission({
-            verdict: input.verdict,
-            body: input.body,
-            comments: input.comments,
-          }),
-        })
-        .pipe(Effect.asVoid);
+      return rest({
+        cwd: input.cwd,
+        host: input.host,
+        operation: "submitReview",
+        method: "POST",
+        path: `repos/${owner}/${name}/pulls/${input.number}/reviews`,
+        body: buildReviewSubmission({
+          verdict: input.verdict,
+          body: input.body,
+          comments: input.comments,
+        }),
+      }).pipe(Effect.asVoid);
     },
 
     replyToReviewThread: (input) =>
```

[Next page](https://git.cafe/pingdotgg/t3code/pull/23909/diffs?after=InBhY2thZ2VzL3NvdXJjZS1jb250cm9sLWdpdGh1Yi9zcmMvc2VydmVyL0dpdEh1YlB1bGxSZXF1ZXN0QXBpLnRzIg&expectedVersion=11&format=markdown)
