Skip to content

gh-wrapper: validate labels before dispatch — prompt interactively, abort with valid labels otherwise #323

Description

@twistedmelonman

Problem

gh fails outright when a label does not exist, and the error does not tell you
what the valid labels are. The usual recovery is to run gh label list, eyeball
the difference, and retry — every time.

$ gh issue edit 30 --add-label tech-dbet
could not add label: 'tech-dbet' not found

Nothing is broken by this, and nothing is lost — the command fails cleanly. The
cost is that the failure arrives at the point of use with no way forward in it,
and a typo is indistinguishable from a label that genuinely does not exist yet.

Proposed behavior

When the invocation carries a label argument, resolve the label before handing
off to real gh:

  • Label exists → proceed unchanged. No prompt, no added latency beyond the
    lookup.
  • Missing, interactive (stderr is a TTY) → prompt to create it, or correct
    to a near match from the repo's existing labels.
  • Missing, non-interactive (piped, CI, agent-driven) → abort with a message
    naming the unknown label and listing valid ones. Never create a label without
    a human, and never silently drop it.

The split matters because most gh traffic here is agent-driven. A prompt that
an agent cannot answer is a hang, so the non-interactive branch has to fail
fast with the information needed to fix the call.

Where it fits

The wrapper already has the two pieces this needs:

  1. Subcommand interception — _gh_wrapper_force_draft_for_off_org() parses
    argv into sub/subsub, skipping flags and flag-values, and returns a
    possibly-modified NUL-delimited argv. That argv parser is the reusable part;
    a label check wants the same walk with a different predicate.

  2. Interactivity detection — the wrapper already tests [[ -t 2 ]] (line
    ~836) to decide whether gh owns the terminal. Same test decides
    prompt-vs-abort here.

Scope — where labels actually appear

Worth enumerating before implementing, because it is wider than gh label:

  • gh issue create/edit --label/--add-label/--remove-label
  • gh pr create/edit --label/--add-label/--remove-label
  • gh label create/edit/delete/clone
  • Repeated flags (--add-label a --add-label b) and comma-joined values
    (--add-label a,b) — both are valid gh and both need splitting
  • --label also exists on gh issue list / pr list as a filter, where a
    non-existent label is a legitimate query that returns nothing, not an error.
    Prompting there would be wrong.

That last case is the one most likely to cause a regression: the same flag name
means "attach this" on create/edit and "filter by this" on list.

Design notes

  • Removal should probably not prompt to create. --remove-label naming a
    missing label is already a no-op; offering to create it inverts the intent.
  • Cost per call: resolving labels is an extra API round-trip on commands
    that currently make one. Cache per repo for the process lifetime, and skip
    the lookup entirely when no label flag is present, so the common path is
    unaffected.
  • Near-match suggestion wants a cheap distance check over the existing label
    list. Worth keeping conservative — suggest only on a close match, since a
    confidently wrong suggestion is worse than none.
  • --repo affects which label set applies. The wrapper already resolves
    owner/repo from argv in _gh_wrapper_resolve_owner(); the lookup must use the
    same resolution, or it will validate against the wrong repo's labels when
    -R is passed from an unrelated directory.
  • Failure of the lookup itself (network, auth, rate limit) must not block
    the underlying command. Fall through to real gh and let it decide, rather
    than converting a transient API problem into a hard failure gh would not
    have produced.

Testing

The existing wrapper tests are the model. Cases worth covering: existing label
passes through untouched; missing label aborts non-interactively with a
listing; comma-joined and repeated flags both split; issue list --label
filtering is left alone; --remove-label on a missing label does not prompt;
lookup failure falls through rather than blocking.

Per the repo's usual discipline, validate any check against a known-bad case
first — a label validator that never rejects anything passes a happy-path test
suite exactly like a correct one.

Status

Write-up only, from a session on twistedmelonman/claude-config. Not scheduled
and not started. Filed so the design notes survive the context they came from.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions