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:
-
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.
-
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.
Problem
ghfails outright when a label does not exist, and the error does not tell youwhat the valid labels are. The usual recovery is to run
gh label list, eyeballthe difference, and retry — every time.
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:lookup.
to a near match from the repo's existing labels.
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
ghtraffic here is agent-driven. A prompt thatan 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:
Subcommand interception —
_gh_wrapper_force_draft_for_off_org()parsesargv into
sub/subsub, skipping flags and flag-values, and returns apossibly-modified NUL-delimited argv. That argv parser is the reusable part;
a label check wants the same walk with a different predicate.
Interactivity detection — the wrapper already tests
[[ -t 2 ]](line~836) to decide whether
ghowns the terminal. Same test decidesprompt-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-labelgh pr create/edit --label/--add-label/--remove-labelgh label create/edit/delete/clone--add-label a --add-label b) and comma-joined values(
--add-label a,b) — both are validghand both need splitting--labelalso exists ongh issue list/pr listas a filter, where anon-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
--remove-labelnaming amissing label is already a no-op; offering to create it inverts the intent.
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.
list. Worth keeping conservative — suggest only on a close match, since a
confidently wrong suggestion is worse than none.
--repoaffects which label set applies. The wrapper already resolvesowner/repo from argv in
_gh_wrapper_resolve_owner(); the lookup must use thesame resolution, or it will validate against the wrong repo's labels when
-Ris passed from an unrelated directory.the underlying command. Fall through to real
ghand let it decide, ratherthan converting a transient API problem into a hard failure
ghwould nothave 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 --labelfiltering is left alone;
--remove-labelon 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 scheduledand not started. Filed so the design notes survive the context they came from.