Skip to content

gh — Fix Report

Generated: 2026-05-07 CLI version: 2.88.1 Scope: Critical failure modes In findings: 13 failure modes evaluated

Summary

Severity Pass (3/3) Partial (1–2) Fail (0)
Critical 3 7 2
High 1 0 0
Medium

Required Fixes (score < 3, sorted: severity desc, score asc)

§1 — Exit Codes & Status Signaling [Critical · 0/3]

Gap: HTTP 4xx responses (401, 404), GraphQL errors, and resource-not-found conditions all exit 0 — contradicting gh's own documented exit code convention that exit 4 is used for auth failures.

Solutions: Map HTTP status codes to semantic exit codes before surfacing errors: - HTTP 401/403 → exit 4 (auth required / permission denied) - HTTP 404 → exit 5 (not found) - HTTP 429 → exit 9 (rate limited) - GraphQL errors → parse the errors[].type field and map to the appropriate exit code

Define and document a complete exit code table in gh help exit-codes with all codes gh can emit.

Requirements that address this: - REQ-F-001 (P0) — Standard Exit Code Table [Tier: F = framework handles] - REQ-F-002 (P0) — Exit Code 2 Reserved for Validation Failures [Tier: F = framework handles]


§53 — Credential Expiry Mid-Session [Critical · 0/3]

Gap: Invalid or expired GH_TOKEN produces a human-readable stderr message (HTTP 401: Bad credentials) and exits 0. Agents cannot detect auth failure programmatically, and the suggested recovery (gh auth login) is interactive-only.

Solutions: - Exit 4 on any authentication failure, consistently - Emit a structured JSON error to stdout (or stderr when --json is active) with code: "AUTH_EXPIRED" or "AUTH_REQUIRED", expired_at if known, and reauth_env_var: "GH_TOKEN" - Never suggest gh auth login in non-TTY context — suggest GH_TOKEN=<value> instead

Requirements that address this: - REQ-F-001 (P0) — Standard Exit Code Table [Tier: F = framework handles] - REQ-C-001 (P0) — Command Declares Exit Codes [Tier: C = you declare it]


§12 — Idempotency & Safe Retries [Critical · 1/3]

Gap: Mutating commands (gh issue create, gh pr create, gh release create) offer no idempotency key, no effect field in output, and no --dry-run flag. Retrying a failed create will produce duplicates.

Solutions: - Add --idempotency-key <string> to all create commands; pass through to GitHub API's idempotency header where supported - Include "effect": "created" | "noop" in JSON output so agents can detect duplicate suppression - Add --dry-run to all mutating commands; output a "would_create" effect with the resource that would be created

Requirements that address this: - REQ-C-007 (P1) — Mutating Commands Accept Idempotency Key [Tier: C = you declare it] - REQ-C-003 (P0) — Mutating Commands Declare Effect Field [Tier: C = you declare it] - REQ-C-004 (P0) — Destructive Commands Must Support Dry-Run [Tier: C = you declare it]


§45 — Headless Authentication [Critical · 1/3]

Gap: When stored credentials are invalid, the error is plain-text stderr and exits 0. The recovery suggestion (gh auth login) requires a browser. Agents have no structured signal and no non-interactive recovery path.

Solutions: - In non-TTY context, emit a structured AUTH_REQUIRED JSON error with auth_methods: [{"type": "env_var", "name": "GH_TOKEN"}] - Exit 4 consistently on all auth failures - Document GH_TOKEN as the canonical non-interactive auth method in AGENTS.md

Requirements that address this: - REQ-F-001 (P0) — Standard Exit Code Table [Tier: F = framework handles] - REQ-C-005 (P0) — Interactive Commands Must Support --yes / --non-interactive [Tier: C = you declare it]


§2 — Output Format & Parseability [Critical · 2/3]

Gap: --json works but requires an explicit field list on every invocation and is not auto-activated in non-TTY. No response envelope (ok/error/meta) — success and failure responses have different shapes.

Solutions: - Auto-activate --json (all fields) when stdout is not a TTY, consistent with CI=true detection - Wrap all JSON output in a standard envelope: {"ok": true, "data": [...], "error": null, "meta": {"duration_ms": N}} - On error, emit {"ok": false, "data": null, "error": {"code": "...", "message": "..."}} to stdout (not only stderr)

Requirements that address this: - REQ-F-003 (P0) — JSON Output Mode Auto-Activation [Tier: F = framework handles] - REQ-F-004 (P0) — Consistent JSON Response Envelope [Tier: F = framework handles]


§10 — Interactivity & TTY Requirements [Critical · 2/3]

Gap: No --non-interactive flag. Headless operation requires setting GH_PROMPT_DISABLED=1 and GH_PAGER=cat — env vars not prominently documented for agent use, with no fallback if they are missed.

Solutions: - Add --non-interactive flag: exits 4 with a structured error if any prompt would be shown - Add --no-pager flag as a first-class flag (currently only suppressible via env var) - Document both in AGENTS.md under Non-Interactive Flags

Requirements that address this: - REQ-C-005 (P0) — Interactive Commands Must Support --yes / --non-interactive [Tier: C = you declare it] - REQ-F-009 (P0) — Non-Interactive Mode Auto-Detection [Tier: F = framework handles] - REQ-F-010 (P0) — Pager Suppression [Tier: F = framework handles]


§11 — Timeouts & Hanging Processes [Critical · 2/3]

Gap: No --timeout flag; no documented timeout env var; long API operations can hang indefinitely; no structured timeout exit code.

Solutions: - Add --timeout <duration> flag (e.g. --timeout 30s) to all commands - On timeout: exit 7, emit {"ok": false, "error": {"code": "TIMEOUT", "duration_ms": N}} - Document the default per-command timeout in --help

Requirements that address this: - REQ-F-011 (P0) — Default Timeout Per Command [Tier: F = framework handles] - REQ-F-012 (P0) — Timeout Exit Code and JSON Error [Tier: F = framework handles]


§43 — Tool Output Result Size Unboundedness [Critical · 2/3]

Gap: Default limit is 30 (good), but JSON output contains no pagination metadata — agents cannot tell whether results were truncated or whether a next page exists.

Solutions: - Include pagination metadata in all list command JSON responses: "meta": {"total_count": N, "has_next_page": true, "next_cursor": "..."} - Add --max-output <bytes> flag; set meta.truncated: true when triggered

Requirements that address this: - REQ-F-018 (P0) — Pagination Metadata on List Commands [Tier: F = framework handles] - REQ-F-019 (P0) — Default Output Limit [Tier: F = framework handles]


§62 — $EDITOR and $VISUAL Trap [Critical · 2/3]

Gap: Commands like gh issue create without --body invoke $EDITOR. Bypass requires supplying all content flags — correct but not documented for agent use; no EDITOR_REQUIRED error emitted in non-TTY when flags are missing.

Solutions: - In non-TTY mode, if editor would be invoked: exit 4 with {"code": "EDITOR_REQUIRED", "alternatives": ["gh issue create --body <text>", "gh issue create --body-file <path>"]} - Set GH_EDITOR=/bin/true automatically in non-TTY mode so a missed flag fails fast rather than blocking - Document in AGENTS.md: "always pass --body or --body-file; never rely on editor"

Requirements that address this: - REQ-F-009 (P0) — Non-Interactive Mode Auto-Detection [Tier: F = framework handles] - REQ-C-005 (P0) — Interactive Commands Must Support --yes / --non-interactive [Tier: C = you declare it]


Already Passing

§8 ANSI & Color Code Leakage, §50 Stdin Consumption Deadlock, §60 OS Output Buffer Deadlock, §64 Headless Display and GUI Launch Blocking (score 3/3 — no action needed)