From 1c6e42d6aace8311a21f11a231108d738b12eb38 Mon Sep 17 00:00:00 2001 From: iamnbutler Date: Thu, 22 Oct 2026 17:37:06 -0400 Subject: [PATCH 1/2] fix(host): list the GitHub CLI and its sign-in in Host tools and the setup checks Why: apps/app/src/team-setup.tsx#function Checks | hello's GitHub login can arrive before gh answers and isn't resent, so the setup check reads gh's sign-in from diagnostics --- apps/app/src/host-settings.tsx | 6 +++++- apps/app/src/team-setup.tsx | 21 +++++++++++++++------ apps/host/src/diagnostics.ts | 29 ++++++++++++++++++++++++++++- apps/host/src/github.ts | 13 +++++++++---- apps/host/src/protocol.ts | 2 +- 5 files changed, 58 insertions(+), 13 deletions(-) diff --git a/apps/app/src/host-settings.tsx b/apps/app/src/host-settings.tsx index 951bbba4..b639547f 100644 --- a/apps/app/src/host-settings.tsx +++ b/apps/app/src/host-settings.tsx @@ -242,7 +242,11 @@ export function HostStatus({ active }: { active: boolean }) { description={tool.path} footer={tool.error && {tool.error}} > - {(tool.version || !tool.path) && {tool.version || "Not found"}} + {(tool.version || tool.account || !tool.path) && ( + + {tool.version || (tool.account ? `Signed in as ${tool.account}` : "Not found")} + + )} ))} diff --git a/apps/app/src/team-setup.tsx b/apps/app/src/team-setup.tsx index 8cc00b6e..77fbd8fd 100644 --- a/apps/app/src/team-setup.tsx +++ b/apps/app/src/team-setup.tsx @@ -611,6 +611,9 @@ function Join({ initial, deployed, onJoined, back }: { export function Checks({ github }: { github?: string }) { const { value } = useDiagnostics(true); const tailscale = value?.tailscale; + const cli = value?.tools.find((tool) => tool.name === "GitHub CLI"); + // hello's login can arrive before gh answers and isn't resent, so a fresh host's check reads gh here. + const login = cli?.account || github; return (
@@ -620,12 +623,18 @@ export function Checks({ github }: { github?: string }) { ? "isn't connected" : "…"} - - {github ? `GitHub · ${github}` : ( - <> - GitHub: run gh auth login - - )} + + {login ? `GitHub · ${login}` : cli && !cli.path + ? ( + <> + GitHub: install GitHub CLI, then run gh auth login + + ) + : ( + <> + GitHub: run gh auth login + + )}
); diff --git a/apps/host/src/diagnostics.ts b/apps/host/src/diagnostics.ts index ac997826..ed5f7d00 100644 --- a/apps/host/src/diagnostics.ts +++ b/apps/host/src/diagnostics.ts @@ -6,6 +6,7 @@ import { config } from "./config"; import * as directory from "./directory"; import * as served from "./directory-server"; import { shell as environment } from "./environment"; +import * as github from "./github"; import { latest } from "./keys"; import type { Diagnostics } from "./protocol"; import * as tailnet from "./tailnet"; @@ -41,6 +42,32 @@ async function shell(): Promise { } } +async function gh(): Promise { + const name = "GitHub CLI"; + const path = Bun.which("gh", { PATH: process.env.PATH }) || undefined; + if (!path) { + return { name, error: "Install GitHub CLI, then sign in with gh auth login, to use GitHub." }; + } + const child = Bun.spawn([path, "auth", "status", "--hostname", "github.com", "--json", "hosts"], { + env: github.environment(), + stdin: "ignore", + stdout: "pipe", + stderr: "ignore", + timeout: 5000, + }); + const [out, code] = await Promise.all([new Response(child.stdout).text(), child.exited]); + if (code !== 0) return { name, path, error: "GitHub CLI could not start." }; + const { hosts } = JSON.parse(out) as { + hosts: Record; + }; + const account = hosts["github.com"]?.find((account) => account.active); + if (!account) return { name, path, error: "Sign in with gh auth login to use GitHub." }; + if (account.state !== "success" || !account.login) { + return { name, path, error: "GitHub didn't accept this sign-in. Run gh auth login again." }; + } + return { name, path, account: account.login }; +} + export async function diagnostics(sync?: boolean): Promise { const secret = latest("ACE_SECRET", sync); if (sync) { @@ -49,7 +76,7 @@ export async function diagnostics(sync?: boolean): Promise { if (!(await secret).waiting) await Promise.race([synced, Bun.sleep(25_000)]); } const [tools, tailscale] = await Promise.all([ - Promise.all([git(), shell()]), + Promise.all([git(), gh(), shell()]), tailnet.diagnostics(started), ]); const { hosted, app, compute, hosts } = directory.read(); diff --git a/apps/host/src/github.ts b/apps/host/src/github.ts index f1818666..cbd0de3c 100644 --- a/apps/host/src/github.ts +++ b/apps/host/src/github.ts @@ -147,9 +147,8 @@ function sentence(error: string): string { return error.replace(/^GraphQL: /, "GitHub refused the request: "); } -async function gh(args: string[]): Promise { - const path = Bun.which("gh", { PATH: process.env.PATH }); - if (!path) throw new Error("Install GitHub CLI on this host, then sign in with gh auth login."); +/** The environment gh runs in: this host's GitHub token, github.com, and no prompts. */ +export function environment(): NodeJS.ProcessEnv { const env: NodeJS.ProcessEnv = { ...process.env, ...githubEnv(), @@ -158,8 +157,14 @@ async function gh(args: string[]): Promise { GH_PAGER: "cat", }; delete env.GH_REPO; + return env; +} + +async function gh(args: string[]): Promise { + const path = Bun.which("gh", { PATH: process.env.PATH }); + if (!path) throw new Error("Install GitHub CLI on this host, then sign in with gh auth login."); const child = Bun.spawn([path, ...args], { - env, + env: environment(), stdin: "ignore", stdout: "pipe", stderr: "pipe", diff --git a/apps/host/src/protocol.ts b/apps/host/src/protocol.ts index 5d72279b..9739d4b3 100644 --- a/apps/host/src/protocol.ts +++ b/apps/host/src/protocol.ts @@ -39,7 +39,7 @@ export type WorkerInfo = { id: string; pid: number }; export type WorkerRequest = { op: "worker" } | { op: "freeze" } | { op: "export" }; export type Diagnostics = { - tools: { name: string; path?: string; version?: string; error?: string }[]; + tools: { name: string; path?: string; version?: string; account?: string; error?: string }[]; tailscale: { state: "missing" | "offline" | "running" | "error"; name?: string; From d41111257e09408515bc2fdabdba99d0a6c3161a Mon Sep 17 00:00:00 2001 From: iamnbutler Date: Thu, 22 Oct 2026 17:37:16 -0400 Subject: [PATCH 2/2] lane: sync fix-gh-check memory --- ...QBFM1R674ESDP4DR9-hello-s-github-login-can-arr.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .lane/memory/apps/app/src/team-setup.tsx/01M5JRQPYQBFM1R674ESDP4DR9-hello-s-github-login-can-arr.md diff --git a/.lane/memory/apps/app/src/team-setup.tsx/01M5JRQPYQBFM1R674ESDP4DR9-hello-s-github-login-can-arr.md b/.lane/memory/apps/app/src/team-setup.tsx/01M5JRQPYQBFM1R674ESDP4DR9-hello-s-github-login-can-arr.md new file mode 100644 index 00000000..5a94d9f0 --- /dev/null +++ b/.lane/memory/apps/app/src/team-setup.tsx/01M5JRQPYQBFM1R674ESDP4DR9-hello-s-github-login-can-arr.md @@ -0,0 +1,12 @@ +--- +id: 01M5JRQPYQBFM1R674ESDP4DR9 +anchor: function Checks +created: 2026-10-22T21:37:06Z +norm: '1' +sig: afce47395c279e36 +body_hash: e0c2b3947f605c0a +raw_hash: 9875d4d01a08d2a7 +lines: 611-641 +--- + +hello's GitHub login can arrive before gh answers and isn't resent, so the setup check reads gh's sign-in from diagnostics