Repository navigation
Add JSON output to gh auth status - #11544
Conversation
gh auth status
|
This look great to me! |
babakks
left a comment
There was a problem hiding this comment.
Thanks for the PR, @benjlevesque! 🙏
I haven't yet fully reviewed it, just had a comment on the -t conflict case to trigger a discussion.
Signed-off-by: Babak K. Shandiz <[email protected]>
@srid, I'm not sure what exactly you mean by signed in (e.g. as in having a valid/working token, or just have signed in at some point), but please note that:
|
|
Ah, I just noticed that
If so, how can the JSON be restructured to reliably get that active token? In the current schema, it is possible for arbitrary number of tokens to be active, since it is just a list. |
Basically, I want to make sure that the user has |
Yes, only one entry per hostname can be active. You can run the command with UPDATE: if you already know the hostname, technically you can try |
This MR contains the following updates: | Package | Update | Change | |---|---|---| | [cli/cli](https://github.com/cli/cli) | minor | `v2.80.0` -> `v2.81.0` | MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot). **Proposed changes to behavior should be submitted there as MRs.** --- ### Release Notes <details> <summary>cli/cli (cli/cli)</summary> ### [`v2.81.0`](https://github.com/cli/cli/releases/tag/v2.81.0): GitHub CLI 2.81.0 [Compare Source](cli/cli@v2.80.0...v2.81.0) #### Support for GitHub Release Attestations This release introduces the `release verify` and `release verify-asset` commands for verifying GitHub Release attestations. Part of the Immutable Releases initiative, a release attestation provides a signed, timestamped binding between a release, its git tag, and any associated assets. These new commands provide a convenient way to verify the integrity of an immutable release against its attestation. - Verify the latest release has a valid attestation: `gh release verify` - Verify a specific release by tag: `gh release verify v1.2.3` - Verify an asset from the latest release: `gh release verify-asset my-asset.zip` - Verify a local asset file originated from a specific release: `gh release verify-asset v1.2.3 my-asset.zip` These commands help ensure that releases and their assets are authentic and haven’t been tampered with, providing an additional layer of security for your software supply chain. #### `gh auth status` Supports JSON Output This release adds support for the `--json` flag in `gh auth status`. Run `gh auth status --help` for more information and usage examples. #### What's Changed ##### ✨ Features - Add alias `co` for `pr checkout` by [@​babakks](https://github.com/babakks) in [#​11804](cli/cli#11804) - Add JSON output to `gh auth status` by [@​benjlevesque](https://github.com/benjlevesque) in [#​11544](cli/cli#11544) - `release verify` and `release verify-asset` are now visible by [@​ejahnGithub](https://github.com/ejahnGithub) in [#​11801](cli/cli#11801) ##### 🐛 Fixes - Fix no tagname logic for release verify-asset by [@​ejahnGithub](https://github.com/ejahnGithub) in [#​11798](cli/cli#11798) ##### 📚 Docs & Chores - refactor: use strings.FieldsFuncSeq to reduce memory allocations by [@​juejinyuxitu](https://github.com/juejinyuxitu) in [#​11805](cli/cli#11805) #### New Contributors - [@​juejinyuxitu](https://github.com/juejinyuxitu) made their first contribution in [#​11805](cli/cli#11805) **Full Changelog**: <cli/cli@v2.80.0...v2.81.0> </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this MR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box --- This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xMzIuNSIsInVwZGF0ZWRJblZlciI6IjQxLjEzMi41IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiXX0=-->
The GitHub CLI Live State probe chained command -v gh, gh auth
status, and echo fallbacks with &&/||/{}, which a Claude Code
worktree-isolation guard refuses to run ("too complex to verify
that it stays inside the worktree"), breaking /smith-preflight
(and by extension /smith-ship step 0) for every worktree-isolated
session.
Split into two unchained probes: command -v gh, and gh auth
status --json hosts --active --jq '...state...' — using gh's own
documented machine-readable field instead of prose-matching,
scoped to the active account, requires gh >= 2.81.0 (cli/cli#11544).
Assisted-by: Claude:claude-sonnet-5
* feat(auth): GhCli で gh から認証状態とトークンを取る detect / auth_status / token の 3 本。実物の `gh` 2.100.0 と cli/cli のソースで 形を確かめてから書いた。 **テキスト出力は解析しないと決めた(設計変更)。** 04 は「JSON とテキスト両対応」と 書いていたが、`gh auth status --json hosts` が入ったのは 2.81.0(2025-10-01 リリース。cli/cli#11544 のマージが 2025-09-25。`gh api` でリリースを引いて確認)で、 `--user` も同じ版に既にある(v2.81.0 の `token.go` を読んで確認)。それ未満は `AuthError::GhTooOld` で degraded に落とす。 理由は**書式を確かめられないまま書くことになる**から。手元の 2.100.0 のテキストは `✓ Logged in to github.com account <login> (<source>)` / `- Active account: true` / スコープはクォート付きだが、古い版は `as <login>` でクォート無し、 `- Active account:` の行自体が無い。再現できない書式にパーサを書くのは `.claude/rules/verifying-assumptions.md` に反する。`gh` はもともと必須依存なので、 要求版を案内するほうが行動可能。古い gh の利用者が出てきたら、その人の実出力を フィクスチャにして足すほうが確実。 実物で分かって設計に反映したこと。 - **同じ `login` が複数のエントリで出る。** 実機で `GITHUB_TOKEN`(環境変数)と `keyring` が、どちらも同じ login で並んだ。`GhLogin` は `tokenSource` を持たない ので**見分けられない重複**になり、`resolve` の「1 件なら `Single`」が誤爆する。 `(host, login)` で畳んで `active` は OR を取る - **`scopes` はカンマ区切りの 1 本の文字列**で `omitempty`。fine-grained PAT では 丸ごと欠ける(04 の EMU の節が「`scopes` が空でも動作させる」と言っている形) - **`state` は `success` / `timeout` / `error` の 3 値**(v2.100.0 の `status.go`)。 採るのは `success` だけ。`timeout` を `error` と同じに畳んでよいかは `auth::resolve` へ送った - **`--show-token` は付けない。** 付けると JSON に生のトークンが入る(不変ルール 3) - `bin: PathBuf` は持たない。差し替える要求が無く、#21 で `Runner.git_bin` を 同じ理由で消してある テストは 13 本。版の比較と JSON の畳み込みは純粋関数として、**残り 3 本は実物の `gh` を叩く**(`ssh` のテストと同じ方針)。`gh` は設定ディレクトリを環境変数でしか 受けないので、`Runner::with_env` と同じ形の `GhCli::with_env` を足し、 `GH_CONFIG_DIR` とトークンの環境変数で実行環境から切り離した。 Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_012MHJpyKE69BFZ2wdU3mfkN * refactor(auth): /simplify の指摘を反映する 4 観点で 20 件。実質的なものが 4 つあった。 **`config` の `general.gh_bin` が黙って死んでいた。** `"gh"` を直書きしていたので、 設定・既定値・`MEWGIT_GH_BIN` の上書きとそのテスト 3 本が実装に到達していなかった。 **しかも根拠に引いた #21 が逆だった** —— 実際のコミット本文は「`Runner` に `git_bin` を **戻し**、`config` の `general.git_bin` から渡す形にした。設計書 01 と 08 は最初から 『ランタイムが読む』と書いており…」で、#21 はまさに今回と同じ穴を塞いだ PR。 `GhCli::detect(bin)` にして、#21 と同型の見張り(直書きに戻すと落ちるテスト)を足した。 04 と doc コメントの誤った引用も直し、08 の「任意コード実行になる」を 2 つの bin で数えた。 **`state` を gh の層で絞ると、04 が `resolve` へ送った判断の材料が届かない。** 04 は「`Timeout` を `Error` と同じに畳んでよいかは `auth::resolve` で決める」と 書いているのに、`success` だけ採って `GhLogin` に `state` を持たせていなかった。 `LoginState` を足して落とさずに運ぶ。畳むときは `state` が一番良かったものを採り、 **スコープは `Success` なエントリのものを採る** —— 出どころが違えば別のトークンなので スコープも違いうる(「同じはず」は確かめていない断定だった)。 **`auth_status` が全ホストを検証していた。** `--hostname` を付けないと gh は config にある全ホストのトークンを検証するので、到達できないホストが 1 つあるだけで **実測 30.2 秒**(絞れば 0.24 秒)。gh に待ち時間を縮めるフラグは無い。04 の手順 2 が 言う「`target.host` で絞る」を、結果ではなく argv でやる形にした。config に無い ホストでも exit 0 で `{"hosts":{}}` が返るので「0 件なら `NotLoggedIn`」は変わらない。 **「証」を名乗る型が検査を通さずに作れた。** `Default` + `pub with_env` があったので `GhCli::default().auth_status()` が通り、テスト自身がその抜け道を使っていた(04 には 「持つ値は無い」と書いてしまっていた)。構築経路を `detect` 1 本に絞り、版判定を 純粋関数 `check_version` に切り出して **`GhTooOld` の腕を初めてテストで踏めるように**した。 `ssh.rs` が「`GhCli` が 3 本目になったら数え直す」と送っていた宿題も片付けた。数えると 3 本ではなく「同形が 2 本 + 別物が 1 本」で、`git_exec::Runner::spawn` は前提が違う (`process_group`、`kill_on_drop` を敢えて付けない、stdin パイプ、キャンセル)。 `ssh -G` と `gh` を `auth::probe` に寄せた結果、**共有していなかったぶんの実費**も 消えた —— `LC_ALL=C` が無い、起動失敗の `io::Error` を捨てて全部 `GhMissing` (権限エラーまで「見つかりませんでした」になっていた)、exit code を落としている。 そのほか。 - `AuthError::GhTooOld` から `required` を落とす(常に `REQUIRED` からの導出値)。 `REQUIRED` を `pub` にして `ui` からも読めるようにした - `merge_logins` を `BTreeMap` にして、線形探索・末尾の `sort_by`・「順は不定」の コメントを 1 つに畳んだ。捨てるための `scopes` パースも走らなくなった - カンマ区切りスコープの分割を `auth::split_scopes` に寄せた(`github::client` の `needed_scopes` が 2 人目) - stdout のコピーを 1 回に(`from_utf8` で `Vec<u8>` を転用)。`token` も `trim_end` で切り詰めて `String` ごと渡し、平のトークンを載せた中間バッファを増やさない - `parse_version` から「パッチ無しも受ける」を落とした。gh がその形を出す実例を見ていない - `requirements.md` の degraded 事由に「版が古い」を足した(4 つ目) Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_012MHJpyKE69BFZ2wdU3mfkN * fix(auth): /code-review の指摘を直す 5 件。 **login が空のエントリを候補に数えていた。** 期限切れの `GH_TOKEN` を export した ままだと、本物のログインの隣に `{"state":"error","active":true,"login":""}` が並ぶ (レビューが実測)。`token(host, user)` は login で引くので**取り出す術が無い**のに 候補として数えるので、04 の手順 3「1 件なら `Single`」が誤爆する。落とすようにした。 **`state` の絞り込みとは違う** —— こちらは `resolve` の方針で変わる余地が無い (どの方針でも使えない)ので、gh の層で落としてよい。 **`scopes.is_empty()` を「まだ埋めていない」の印にしていた。** fine-grained PAT は 本当にスコープが空なので(04 の EMU の節)、1 件目がそれで 2 件目が別のトークンだと、 **持っていないスコープを報告する**。「足りないスコープ」の案内が嘘になる。 `scopes_taken` を別に持って、印と値を割らない形にした。 **`GhMissing` の案内にパスが入っていなかった。** `general.gh_bin` や `MEWGIT_GH_BIN` の書き間違いでもここに来るので、「gh が見つかりませんでした」だけだと PATH を 探させてしまう。 **04 に古い段落が残っていた。** 「`bin: PathBuf` は持たない … `Command::new("gh")` の PATH 解決に任せる」が、48 行上の構造体と、同じコミットで足した箇所の両方と矛盾した まま生き残っていた。しかも**直前のコミットが「誤りだった」と書いた #21 の逆読み**が そこに残っていたので、次に読む人が `bin` を消して同じ穴を開け直せる状態だった。 **版のぶら下がりで全機能を落としていた。** `2.85.0+dfsg1` や `2.90.0-rc.1` が `GhTooOld` に落ちる。**`/simplify` の「実例を見ていない一般性は入れない」と逆を 向く指摘だが、実害がある側を採った** —— 厳しくして外すと十分新しい gh で GitHub 機能を 丸ごと失い、緩めて外しても比較に使う 3 つの数は変わらない。3 つ揃っていることは 引き続き求め、**パッチが数字だけで終わっているのに 4 つ目が続く形**(`2.81.0.1`)は 知らない採番なので受けない。 Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_012MHJpyKE69BFZ2wdU3mfkN --------- Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
…-in refused in CLI and routes, the broker's identity shown, sign-out of a leftover personal account (#66) * Team Environment: gh is not signed in with a person's account; a person's account left there can be signed out Principal 2026-09-28: a Team Environment (hosted-organization-team) works in GitHub through Lazurio for GitHub, set up by the Organization. One pure rule (src/tools/team-github.ts, githubActionRefused) refuses the curated gh sign-in and SSH key linking there and allows gh's sign-out only while the active account is a person's stored sign-in (never a [bot] App identity, an installation token or a token from a variable). The CLI reads the preset of the hosted operator Folder (hostedOperatorFolder, as lazurio update does); the Launchpad reads its Folder's preset per request. Refusals are the usual blocked result: reason team-environment, exit 2 / HTTP 409. The gh probe reports identity; the Team gh row offers Sign out only for a person's account and its status line says "uses Lazurio for GitHub". Docs: F19 addendum, environment tools, workspace presets, Launchpad development. Co-Authored-By: Claude Fable 5.1 <[email protected]> * Team Environment: brokered gh reads as the Organization's bot; gh's prompt and install sign nobody in gh's sign-in probe asks `gh auth status --json hosts` first (gh 2.81.0, cli/cli#11544), the one gh auth command the Organization's brokered gh answers, and falls back to the text form for an older gh. One field, never --show-token, so the token is not in the answer. The same reading serves the status, login confirmation, the scope check and the key removal (src/tools/gh-status.ts). On a Team Environment the App identity reads "Works as lazurio-for-github[bot]" next to "Uses Lazurio for GitHub", with no sign-out and a refused logout. `lazurio tools prompt gh` reads the hosted operator Folder's preset and prints gh's Team target state there instead of the sign-in; elsewhere the prompt adds the Team sentence. `tools install gh` on a Team Environment ends with the Team sentence instead of "Next: lazurio tools login gh"; the Launchpad's Team gh row offers "Install" only while gh is missing. The fake gh gains the broker mode and a gh without --json. Docs: F19 addendum, environment tools, Launchpad development; known limits kept. Co-Authored-By: Claude Fable 5.1 <[email protected]> * Team Environment: a running gh session re-checks the rule before every step that changes the account or the Machine Review of PR 66: a gh login started on a Work Folder finished, SSH key linking included, after the preset changed to hosted-organization-team. The login core now asks the rule again (LoginEnvironment.refused) after the device flow, around a scope refresh, before the key linking starts, before the key's creation, its registration, the known_hosts write and the final "signed in", and ends the session as blocked / team-environment. The Launchpad ends a running gh session when /api/update makes its Folder a Team Environment, and a gh poll there ends the session with the refusal. An unreadable preset fails the Launchpad session closed (environment-unreadable). Regression tests: each checkpoint in turn, and a Work-to-Team switch through the real profile update mid-login. Co-Authored-By: Claude Fable 5.1 <[email protected]> * Folder reads wait for the operation lock instead of failing as busy The flaky gh login test of PR 66 (launchpad-curated with update-path-entry, and macOS CI) was not shared test state: under load, the Launchpad's poll gate and the running gh session's Team-rule re-check read the Folder's preset at the same moment, and the Folder operation lock is exclusive and non-blocking (flock LOCK_NB), so one read was refused as busy: the poll answered operation-failed (or the session failed environment-unreadable). Reads (the Team rule's preset, the tools status, readFolderTools, /api/profile) now go through withFolderReadLock, which retries only on the new FolderOperationBusyError for up to 3 s; mutations keep the immediate refusal and the lock messages are unchanged. Regression tests: 24 parallel preset reads, a read waiting for a holder, the bound, and a Launchpad gh login completing while another reader keeps taking the lock. Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: Claude Fable 5.1 <[email protected]>
…e a host by its active account (#203) (#216) Root causes: - gh's human `auth status` output reports every non-timeout error (DNS failure, connection refused) as "X Failed to log in ... token invalid", so offline, doctor said "NOT authenticated". - In a repo the check is scoped (`--hostname H`, #100/#102) and was read from the exit code alone: a timeout on H said "NOT authenticated", and an INACTIVE account with a dead token on H exits 1 as well, so Authed() gated every gh call off (PR lookups, liveness) while the active account worked. Fix: - ghx asks `gh auth status --json hosts --active [--hostname H]` first (gh 2.81+, cli/cli#11544; shape read from gh's source at v2.81.0 and v2.102.0). That form exits 0 whatever the login, keeps gh's error per account, and --active checks only the account gh uses. It is read from stdout alone (pure parseAuthStatusJSON): the active entry decides a host, none marked is unknown. Pure classifyAuthError: only a 401 is a dead token (REST "HTTP 401: ...", GraphQL for GH_TOKEN "non-200 OK status code: 401 ..."); no HTTP answer (`Get "<url>": ...`) is the new state "unreachable"; any other status (a 403, a 5xx) is unknown. - When gh refuses the flag ("unknown flag: --json", before 2.81) the SAME memoized check falls back to the #183 human-form parser, and the refusal is remembered per process (authNoJSON): gh < 2.81 spends one local flag error per process, gh 2.81+ still asks once per command (#172). Any other failure falls back once, not remembered (pure jsonAuthOutcome). - AuthedFor/Authed decide by each host's ACTIVE account (pure authOK), not the exit code. A human-form exit 1 is a yes only when an inactive account's failure in a section wt read explains it (textAuthStatus): Authed() sets PRChecked, which dormancy relies on, so a failure wt cannot see stays a no. Unreadable output stays a no. - doctor reads a scoped check per host too: a timeout or unreachable repo host says "could not be verified" and names it, never "NOT authenticated"; a scoped check wt can't read names its host. A human-form "failed" (gh < 2.81) can still be an unreachable host, so that NOT-authenticated line says "or offline". JSON, additive: gh_hosts[].state gains "unreachable", gh_hosts[] gains "error" (gh's text, 2.81+), and a scoped check that did not pass now carries gh_hosts/gh_auth_unknown; a check that passed is unchanged. Measured with a fake gh first on PATH, one shim per generation (2.102-style JSON; 2.68.1's verbatim refusal of --json), HOME a temp dir, before vs after: offline DNS failure, outside a repo: before, both: gh - found but NOT authenticated (...) after, 2.81+: gh - found, but auth could not be verified: couldn't reach ghe.example.com, couldn't reach github.com after, 2.68: gh - found but NOT authenticated, or offline: gh before 2.81 reports a host it cannot reach as a failed login (...) in a github.com-origin repo: offline: NOT authenticated -> (2.81+) could not be verified: couldn't reach github.com timeout: NOT authenticated -> (both) could not be verified: github.com timed out inactive dead account: NOT authenticated and `wt status` ran no PR lookup -> (both) gh - authenticated, and `wt status` runs `gh pr list` for both windows dead token (401): NOT authenticated before and after (2.68: "or offline") a passing check and the #183 two-host case: the gh line is unchanged. gh runs per doctor: 1 (2.81+), 2 (2.68: the flag error, then the human form). Mutation checks in a scratch copy: 25 mutants (exit code decides, the JSON form's exit code trusted, every gh error a dead token, active marker ignored, no fallback, refusal not remembered, one stream for the JSON run, no ANSI strip, an unexplained exit 1 read as a yes, doctor's pre-#203 scoped reading, the caveat on a 401, ...) are all killed. Fixes #203 Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
This PR adds a
--jsonflag to thegh auth statuscommand.--jqand--templateare also availableResolves #8637
Acceptance criteria: #8637 (comment)