Skip to content

Tolerate login-shell noise in GitHub CLI JSON output#378

Merged
sbertix merged 2 commits into
mainfrom
sbertix/377-gh-fix
Jun 3, 2026
Merged

Tolerate login-shell noise in GitHub CLI JSON output#378
sbertix merged 2 commits into
mainfrom
sbertix/377-gh-fix

Conversation

@sbertix

@sbertix sbertix commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Problem

The GitHub CLI settings pane shows "The data couldn't be read because it isn't in the correct format" even when gh is installed, authenticated, and gh auth status --json hosts returns valid JSON in the user's own terminal.

Cause

runGh invokes gh through a login shell (zsh -l -c '…'). The -l flag sources .zprofile / .zlogin without the redirect that only suppresses .zshrc, so any login-file output (a fastfetch/neofetch banner, a version-manager init line, a plain echo) prepends to captured stdout. JSONDecoder then chokes on the leading noise and throws a DecodingError, which was never wrapped and surfaced verbatim. The same polluted stdout flows through runGh to five JSON decode sites, so PR badges, default-branch resolution, and remote-info also failed (the last one silently).

Fix

  • Enumerate every balanced top-level JSON value ({…} or […]) in the output, skipping openers that never balance (a stray brace from a verbose shell), and decode the last decodable span, since gh prints its JSON after any leading noise.
  • Apply this at all five gh JSON decode sites that share the polluted stdout.
  • A missing payload throws a shell-pollution message; a present-but-undecodable payload throws a version-incompatibility message (only when the span was actually valid JSON) and logs the underlying error via SupaLogger, length-capped, so the offending output is retrievable from the log stream.
  • latestRun / resolveRemoteInfo keep their nil contract (no runs / no remote) while tolerating leading noise and logging a genuine parse failure.
  • Also fixes a latent multi-host bug: auth-status previously scanned an arbitrary first host and could report "not authenticated" for a valid active account on another host. It now prefers github.com, then a stable sort, so selection is deterministic.

Tests

Adds unit coverage for the balanced-span scanner (banner-prefixed object/array, trailing noise, braces and escaped quotes inside strings, two top-level values, a skipped stray opener, pure-noise/empty/unterminated), the deterministic multi-host selection, and end-to-end through the live client that banner-polluted stdout still decodes for authStatus, resolveRemoteInfo, defaultBranch, and latestRun, while missing / bracket-only / undecodable payloads throw their respective errors.

Closes #377

sbertix added 2 commits June 3, 2026 19:32
…e failures

A login shell sources .zprofile/.zlogin before gh runs, so banner or version-manager
output prepends to captured stdout and corrupts the JSON. JSONDecoder then throws a
DecodingError that surfaces in the GitHub settings pane as the opaque 'The data couldn't
be read because it isn't in the correct format', even though gh is installed, authed, and
returns valid JSON in the user's terminal.

Enumerate every balanced top-level JSON value ({...} or [...]) in the output, skipping
openers that never balance (a stray brace from a verbose shell), and decode the last
decodable span since gh prints its JSON after any leading noise. Applied at all five gh JSON
decode sites that share the polluted stdout via runGh. A missing payload throws a
shell-pollution message; a present-but-undecodable one throws a version-incompatibility
message (only when the span was valid JSON) and logs the underlying error. latestRun and
resolveRemoteInfo go through decodeIfPresent so a no-payload result stays nil (no runs / no
remote) while leading noise is tolerated and a genuine parse failure is logged. Also fix the
latent multi-host bug where auth-status scanned an arbitrary host: it now prefers github.com,
then a stable sort, so selection is deterministic even when more than one host is active.

Closes #377
…i-host auth

Cover the balanced-span scanner (clean object, banner-prefixed object and array, trailing
noise, braces and escaped quotes inside strings, two top-level values in order, a stray
unbalanced opener that is skipped, pure noise/empty/unterminated yield no spans), the
deterministic multi-host active-account selection (non-first host, prefer github.com, sort
when github.com absent, no active account), and end-to-end through the live client that
banner-polluted stdout still decodes for authStatus, resolveRemoteInfo, defaultBranch, and
latestRun; that latestRun skips a stray-brace banner and prefers the real array over a
leading empty-array banner; and that a missing payload, bracket-only noise, and an
undecodable payload throw their respective GithubCLIError.commandFailed messages.
@sbertix sbertix enabled auto-merge (squash) June 3, 2026 17:36
@tuist

tuist Bot commented Jun 3, 2026

Copy link
Copy Markdown

🛠️ Tuist Run Report 🛠️

Builds 🔨

Scheme Status Duration Commit
supacode 3m 56s 5c4191855

@sbertix sbertix merged commit b1b65bf into main Jun 3, 2026
2 checks passed
@sbertix sbertix deleted the sbertix/377-gh-fix branch June 3, 2026 17:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: gh cli doesn't work: the data couldn’t be read because it isn’t in the correct format

1 participant