Skip to content

fix(review): fall back across GitButler JSON flag syntaxes (but 0.22.0) - #1216

Merged
backnotprop merged 1 commit into
mainfrom
fix/gitbutler-json-flag-fallback
Aug 5, 2026
Merged

fix(review): fall back across GitButler JSON flag syntaxes (but 0.22.0)#1216
backnotprop merged 1 commit into
mainfrom
fix/gitbutler-json-flag-fallback

Conversation

@backnotprop

Copy link
Copy Markdown
Owner

TLDR: plannotator review in a GitButler workspace dies on GitButler CLI 0.22.0 because but --format json status is rejected with error: unexpected argument '--format' found. The status call now retries exactly once with but --json status when, and only when, the failure is clap's unexpected-argument rejection of the flag we passed. Every other failure still fails loudly with no retry.

Closes #1215

Verified upstream flag history

Checked directly against the but CLI's clap definitions (crates/but/src/args/mod.rs) at the release/0.21.0, release/0.21.2, and release/0.22.0 tags of gitbutlerapp/gitbutler:

  • Pre-0.21 had a --json flag. feat(cli)!: remove --json in favor of --format json gitbutlerapp/gitbutler#14061 (merged 2026-06-05, shipped in 0.21.0) removed it in favor of a global --format <human|agent|shell|json|none> value flag. All of 0.21.0 through 0.21.2 accept only --format json and reject --json as an unexpected argument.
  • Clean up CLI output formats gitbutlerapp/gitbutler#15026 "Clean up CLI output formats" (merged 2026-07-27, shipped in 0.22.0 on 2026-07-30) reverted course: "The only format flag is now --json". 0.22.0 accepts only a global boolean --json and rejects --format as an unexpected argument.
  • 0.22.0 does ship a retired-syntax shim (crates/but/src/retired_syntax.rs), but it only rewrites the retired but commit grammar. It does not translate the global --format flag, so but --format json status genuinely fails on 0.22.0, exactly as reported.

So each supported release rejects the other release's spelling, and both spellings must be handled. The JSON payload shape (mergeBase, stacks, uncommittedChanges) is unchanged between the two flags, as the reporter observed.

Design: narrow capability sniff, one retry, never on real failures

In packages/shared/gitbutler-core.ts:

  • but --format json status stays the primary invocation, so 0.21.x installs see zero behavior change (it also matches GITBUTLER_MIN_VERSION = "0.21.0", which is preserved: 0.21.x accepts the primary syntax, so no version bump is warranted).
  • If the call exits non-zero AND stderr contains clap's signature unexpected argument '--format' for the exact flag we passed, the other spelling (but --json status) is tried exactly once. The sniff is deliberately narrow: any other non-zero exit (locked database, real status failure, etc.) propagates unchanged as a GitButlerContractError, per the module's fail-loud contract philosophy. Retrying arbitrary failures could mask a real error behind a second, confusingly different one, and could double-run a command against a wedged repository.
  • If the retry is also rejected as an unexpected argument, the error names both attempted spellings and the GitButler version requirement.
  • The accepted spelling is remembered per runtime, so a 0.22.0 install pays the failed --format probe only once per session, and the sniff is symmetric from then on (if the preferred spelling ever stops parsing, the other is probed once).
  • Error strings now name the syntax actually used on the failing attempt (GitButler status (but --format json status) failed: ..., and the invalid-JSON parse error carries the same label).

Pi

The reporter hit this on Pi. gitbutler-core is vendored to the Pi extension at build time by apps/pi-extension/vendor.sh (confirmed in its copy list), so the fix flows to Pi with no Pi-side changes; the Pi test suite passes against the regenerated vendor copy.

Tests

New GitButler status JSON flag fallback describe block in packages/shared/gitbutler-core.test.ts:

  • --format json status fails with the exact clap stderr, fallback runs --json status, parse succeeds, review context and workspace diff are built; the remembered spelling skips the failed probe after the cache TTL.
  • --format json status fails with an unrelated error (database locked), no retry happens (asserted on the recorded but invocations), and the contract error propagates.
  • Both spellings rejected as unexpected arguments yields a clear contract error naming both syntaxes and the 0.21.0 version requirement.

Results: bun test packages/shared 736 pass / 0 fail, bun test apps/pi-extension 177 pass / 0 fail, bun run typecheck clean.

Credit

Thanks @swushi for the report, the exact repro on 0.22.0, and for suggesting the capability-based dual-syntax approach this implements.

This fix was developed with AI assistance.

GitButler 0.22.0 removed the global --format flag in favor of --json
(gitbutlerapp/gitbutler#15026), so `but --format json status` now dies
with clap's unexpected-argument error and GitButler review sessions fail
to start. 0.21.x accepts only --format json (gitbutlerapp/gitbutler#14061),
so neither spelling works everywhere.

Keep --format json as the primary invocation and, only when it fails with
clap's narrow unexpected-argument rejection for the exact flag we passed,
retry once with the other spelling. Real status failures never retry and
keep failing loudly per the module's contract-error philosophy. The
accepted spelling is remembered per runtime so 0.22.0 installs pay the
failed probe once. Error strings now name the syntax actually used.

Closes #1215
@backnotprop
backnotprop merged commit b174568 into main Aug 5, 2026
15 checks passed
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.

GitButler review fails with new version 0.22.0 because it requires a newer JSON output flag

1 participant