From f681688f9b3bb38b4a0dea4ef3174854ab0c284a Mon Sep 17 00:00:00 2001 From: Adam Eivy Date: Tue, 18 Aug 2026 12:46:56 -0700 Subject: [PATCH] fix: glab api has no --jq flag, pipe to jq instead Caught live while dogfooding the fix against a real GitLab issue: plain glab api (unlike glab issue/glab mr subcommands) has no built-in --jq flag and errors with "Unknown flag: --jq." Every glab api call now pipes to the standalone jq binary instead. --- commands/do/next.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/commands/do/next.md b/commands/do/next.md index e2b5c75..b15efa1 100644 --- a/commands/do/next.md +++ b/commands/do/next.md @@ -293,7 +293,7 @@ Then: # filters (the explicit-#num path below already compares against this same # `glab api user` value). if [ "$SELF_MODE" = "true" ]; then - ME="$(glab api user --jq .username)" + ME="$(glab api user | jq -r .username)" LIST_ARGS+=(--author "$ME") fi glab issue list "${LIST_ARGS[@]}" --per-page 100 \ @@ -343,10 +343,10 @@ Then: - `epic-empty` (no children resolvable either way) → not really an umbrella; treat as an ordinary issue. 4. **Resolve declared dependencies before picking (blocked-by).** A candidate may declare a hard dependency in its **body**: a line matching `Depends on #` or `Blocked by #` (case-insensitive; one such line may list several, e.g. `Depends on #12, #15`). Collect every `#` referenced on those lines. A candidate is **blocked** when ANY referenced issue is still open — check the freshest state (GitHub: `gh issue view --json state -q .state`; GitLab: `glab issue view --output json --jq .state`) and test for "closed" rather than an exact "open" match, since the two hosts spell it differently (`OPEN`/`CLOSED` vs `opened`/`closed`); a referenced number that is closed, or doesn't exist, does not block. Resolve this **lazily** as you walk the queue (only for the candidate you're about to pick), so a long backlog doesn't fan out a `gh`/`glab` call per issue up front. - `blocked` (≥1 referenced issue still open) → **skip** in auto-pick; note `#N blocked by #M (open)`. The skip is **self-clearing** — when #M closes, #N becomes eligible on the next run with no manual relabel. - - Also honor each host's **native** blocked-by relationship when the API surfaces it — GitHub's GraphQL `blockedBy` connection, or GitLab's Issue Links API filtered to `link_type: "is_blocked_by"` (`glab api projects/:id/issues//links --jq '.[] | select(.link_type == "is_blocked_by")'`, id/iid resolved the same way the rest of this phase resolves them); the body convention is the portable default and the two are OR'd (blocked by *either* source ⇒ skip). + - Also honor each host's **native** blocked-by relationship when the API surfaces it — GitHub's GraphQL `blockedBy` connection, or GitLab's Issue Links API filtered to `link_type: "is_blocked_by"` (`glab api projects/:id/issues//links | jq '.[] | select(.link_type == "is_blocked_by")'` — unlike `glab issue view`/`glab issue list`, plain `glab api` has no built-in `--jq` flag, so pipe to the standalone `jq` binary; id/iid resolved the same way the rest of this phase resolves them); the body convention is the portable default and the two are OR'd (blocked by *either* source ⇒ skip). - **Cycle / unresolvable chain** (A depends on B, B depends on A) → both stay skipped; note the cycle so a human can break it. Never loop trying to resolve one. 5. **Pick the target issue:** - - **With argument** — the issue number (strip `#`); **set `ISSUE_NUM` to that stripped number now** (pulling step 6's assignment earlier so the checks below can reference `$ISSUE_NUM` — on a fresh run it isn't set yet). Verify open and NOT in flight. **`--self` first, as a hard gate:** when `SELF_MODE` is on, confirm the issue's author is the running account before anything else — GitHub: `gh issue view "$ISSUE_NUM" --json author -q .author.login` must equal `gh api --hostname "$GH_HOST" user -q .login`; GitLab: `glab issue view "$ISSUE_NUM" --output json --jq .author.username` must equal `glab api user --jq .username`; if it does not, **refuse and stop** with `Issue # was filed by , not you — /do:next --self only works on issues you filed. Drop --self to claim it.` This is the **one skip an explicit number does NOT override** — `--self` is a security boundary, not a curation preference, so a deliberate cherry-pick cannot cross it (unlike a parking label or label filter). If it's an epic, resolve its state (step 3) first and act on that state — claim an `epic-wrapup`, close an `epic-done`, or warn that children are still open on an `epic-open` (the explicit request still overrides — state that you're doing so). Otherwise a named number is an **explicit override**: it claims even an issue auto-pick would skip — a parking-labelled one, one with an **open declared blocker** (step 4), one outside the curated label when `LABEL_FILTER` is active, or one outside the dispatch-hint filter when `MODEL_FILTER`/`EFFORT_FILTER` is active (but **never** an issue another user filed while `--self` is on). State plainly when you're overriding a skip (e.g. "claiming `future`-labelled #123 by explicit request", "claiming #123 despite open blocker #120 by explicit request", or "claiming `model:heavy` #123 despite --model light by explicit request"). If any other check fails (closed, in flight), print why and stop. + - **With argument** — the issue number (strip `#`); **set `ISSUE_NUM` to that stripped number now** (pulling step 6's assignment earlier so the checks below can reference `$ISSUE_NUM` — on a fresh run it isn't set yet). Verify open and NOT in flight. **`--self` first, as a hard gate:** when `SELF_MODE` is on, confirm the issue's author is the running account before anything else — GitHub: `gh issue view "$ISSUE_NUM" --json author -q .author.login` must equal `gh api --hostname "$GH_HOST" user -q .login`; GitLab: `glab issue view "$ISSUE_NUM" --output json --jq .author.username` must equal `glab api user | jq -r .username` (plain `glab api` has no built-in `--jq` flag, unlike `glab issue`/`glab mr` subcommands — pipe to the standalone `jq` binary instead); if it does not, **refuse and stop** with `Issue # was filed by , not you — /do:next --self only works on issues you filed. Drop --self to claim it.` This is the **one skip an explicit number does NOT override** — `--self` is a security boundary, not a curation preference, so a deliberate cherry-pick cannot cross it (unlike a parking label or label filter). If it's an epic, resolve its state (step 3) first and act on that state — claim an `epic-wrapup`, close an `epic-done`, or warn that children are still open on an `epic-open` (the explicit request still overrides — state that you're doing so). Otherwise a named number is an **explicit override**: it claims even an issue auto-pick would skip — a parking-labelled one, one with an **open declared blocker** (step 4), one outside the curated label when `LABEL_FILTER` is active, or one outside the dispatch-hint filter when `MODEL_FILTER`/`EFFORT_FILTER` is active (but **never** an issue another user filed while `--self` is on). State plainly when you're overriding a skip (e.g. "claiming `future`-labelled #123 by explicit request", "claiming #123 despite open blocker #120 by explicit request", or "claiming `model:heavy` #123 despite --model light by explicit request"). If any other check fails (closed, in flight), print why and stop. - **Without argument** — pick the FIRST candidate in the priority/oldest walk (step 1) that is NOT in flight, NOT already assigned, NOT carrying a parking label (`blocked`, `needs-input`, `wontfix`, `discussion`, `future`, or any repo-specific parking label — skip and note it), NOT blocked by an open declared dependency (step 4 — skip and note it), and NOT an `epic-open`/`epic-done` epic per step 3 (an `epic-wrapup` epic **is** eligible). Because auto-pick is label-agnostic by default, the parking-label skip, the dependency skip, and the epic resolution are the primary guards against claiming parked, blocked, or umbrella work. An explicit `#num` can still claim a skipped issue; auto-pick never surfaces one. 6. **Set `ISSUE_NUM=` and `SLUG="issue-${ISSUE_NUM}"`** — later phases use `SLUG` for worktree/branch/commit/PR and `ISSUE_NUM` for `gh issue`/`glab issue` calls. - **Surface the claimed issue's dispatch hint, if it carries one** (`model:` / `effort:`): `#42 hints model:heavy + effort:high`. In the **single-issue** flow this is a *report, not a dispatch* — a session cannot switch its own model or effort mid-run on any host, so the work proceeds in whatever session you're already in. Say so when there's a real mismatch worth acting on (`this session is on and #42 hints model:heavy — consider restarting on a stronger model, or continue as-is`), naming the mechanism **this** CLI uses to switch models if it has one, and then continue; never stall waiting for permission over an advisory label. Swarm is where the hint is actually *applied*, because that flow spawns a fresh agent per issue (Phase B). @@ -408,7 +408,7 @@ if [ "$CLI_TOOL" = gh ]; then ME="$(gh api --hostname "$GH_HOST" user -q .login)" gh issue edit "$ISSUE_NUM" --add-assignee @me else - ME="$(glab api user --jq .username)" + ME="$(glab api user | jq -r .username)" # plain `glab api` has no built-in --jq flag; pipe to the standalone jq binary # `+` ADDS one assignee without touching whatever's already on the issue. A bare # `--assignee "$ME"` REPLACES the whole assignee list, which would silently # overwrite a sibling who claimed first and defeat the read-back check below.