Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Gh Cli Patterns

ASecurity

Canonical reference for all gh CLI command shapes used by skills in this plugin. Defines the placeholder convention, default-branch (trunk vs git-flow) detection, allowed --json fields, GraphQL fallback rules, -f/-F/--raw-field flag semantics, the PR-readiness gate, code-scanning alert query, review-thread fetch/count/resolve mutations, and heredoc bodies. Prevents Unknown JSON field errors and divergent query shapes.

3 stars
0 votes
0 copies
2 views
Added 9/19/2026
ai-agentsrustshellbashnodegitapidatabasesecurity

Works with

cliapi

Security Analysis

A100/100

Scanned 9/23/2026

$npx -y skills add dryvist/claude-code-plugins --skill gh-cli-patterns --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Gh Cli Patterns?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Gh Cli Patterns
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/dryvist-gh-cli-patterns/badge)](https://www.skillsdirectory.com/skills/dryvist-gh-cli-patterns)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
SKILL.md
---
name: gh-cli-patterns
description: >-
  Canonical reference for all gh CLI command shapes used by skills in this
  plugin. Defines the placeholder convention, default-branch (trunk vs
  git-flow) detection, allowed --json fields, GraphQL fallback rules,
  -f/-F/--raw-field flag semantics, the PR-readiness gate, code-scanning
  alert query, review-thread fetch/count/resolve mutations, and heredoc
  bodies. Prevents Unknown JSON field errors and divergent query shapes.
---

# gh CLI Canonical Patterns — github-workflows

## Placeholder Convention

Two visually distinct notations — never mix them up:

| Notation | Meaning | Example |
|---|---|---|
| `$varName` | GraphQL variable name — **keep as literal text** in the query body | `$prNumber` |
| `<UPPER_NAME>` | Shell template — **replace before running** | `<PR_NUMBER>` |

Standard replacements:

```text
<OWNER>          → $(gh repo view --json owner --jq '.owner.login')
<REPO>           → $(gh repo view --json name  --jq '.name')
<PR_NUMBER>      → $(gh pr view  --json number --jq '.number')  (integer)
<THREAD_ID>      → PRRT_* node ID from the fetch-threads query (string)
<DATABASE_ID>    → numeric comment ID from the fetch-threads query
<DEFAULT_BRANCH> → see Canonical Default-Branch Detection below
```

## Canonical Default-Branch Detection

Repos in this org run one of two branch models. Detect which one before
using any literal `main` in a PR base, sync target, or merge command:

```bash
gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name'
# or, without gh: git remote show origin | grep 'HEAD branch'
```

| `<DEFAULT_BRANCH>` | Model | What it means for skills in this plugin |
|---|---|---|
| `main` | Trunk | `main` is both the default branch and production. `/merge-pr` merges into it with its normal default (merge commit) or `--squash`/`-s`. |
| `develop` | git-flow (see [git-flow rule](https://github.com/dryvist/ai-assistant-instructions/blob/main/agentsmd/rules/git-flow.md)) | `develop` is the default integration branch — feature PRs target it, `/merge-pr`'s default (merge commit) or `--squash` both apply. `main` is production only: **merge commits only**, no direct pushes, every merge triggers release-please. |

**Never infer the model from the current local branch name** — always read
`defaultBranchRef` (or `origin/HEAD`) fresh, since it can differ per repo and
per invocation.

A PR's own base branch (`gh pr view --json baseRefName --jq '.baseRefName'`)
can still be `main` on a git-flow repo — that is a promotion PR (`develop` →
`main`), not a feature PR, and it is merge-commit-only regardless of what
this section's detection returns. See `/promote-release`.

## `gh pr view --json` — REST-Only

`reviewThreads` is **not** a valid `--json` field — it is GraphQL-only. Any
`gh pr view --json reviewThreads` call fails with `Unknown JSON field: "reviewThreads"`.

Other GraphQL-only fields: inline thread structure, resolution status, full
`mergeStateStatus` enum.

**Rule**: if the field isn't returned by `gh pr view --json` (no value), use `gh api graphql`.

## REST vs GraphQL

| Operation | Use |
|---|---|
| Fetch unresolved threads | GraphQL — see Canonical Review-Thread Queries |
| Verify thread resolution count | GraphQL — see Canonical Review-Thread Queries |
| Resolve a thread | GraphQL — `resolveReviewThread` mutation |
| Reply to a thread | GraphQL (`addPullRequestReviewThreadReply`) or REST (simpler for markdown/special chars) |
| Reply to a PR-level comment | REST `repos/<OWNER>/<REPO>/issues/<PR_NUMBER>/comments` |
| PR state fields (`state`, `mergeable`, `mergeStateStatus`, etc.) | `gh pr view --json` if listed; else GraphQL |

## Flag Semantics

| Flag | Use |
|---|---|
| `-f key=value` | String — for the `-f query='...'` GraphQL body and string variables |
| `-F key=value` | Auto-typed — for `Int!` and `Boolean!` GraphQL variables |
| `--raw-field 'key=value'` | Literal string, no `$var` expansion — for queries using inline `<PLACEHOLDER>` substitution |

**Never interpolate shell `$VARS` inside a GraphQL query string.** Declare typed variables
with `-f`/`-F` instead.

## Canonical PR-Readiness Gate

Use `first: 100` (never `first: 25` or `last: 100`). Always include `pageInfo`.

Replace `<OWNER>`, `<REPO>`, `<PR_NUMBER>` before running (see Placeholder Convention above).

```bash
gh api graphql -f query='
  query($owner:String!,$repo:String!,$prNumber:Int!){
    repository(owner:$owner,name:$repo){
      pullRequest(number:$prNumber){
        state mergeable mergeStateStatus isDraft reviewDecision
        labels(first:100){nodes{name} pageInfo{hasNextPage}}
        commits(last:1){nodes{commit{statusCheckRollup{state}}}}
        reviewThreads(first:100){nodes{isResolved} pageInfo{hasNextPage}}
      }
    }
  }' -f owner=<OWNER> -f repo=<REPO> -F prNumber=<PR_NUMBER>
```

Inside the `-f query='...'` body, `$owner`/`$repo`/`$prNumber` are GraphQL variable names —
keep them literal. After the closing `'`, `-f owner=<OWNER>` etc. bind values — replace the
`<ANGLE_BRACKET>` placeholders with actual strings.

Required values — abort if any fail:

| Field | Required | Abort message |
|---|---|---|
| `state` | `OPEN` | "PR is not open" |
| `mergeable` | `MERGEABLE` | "PR has git conflicts" |
| `mergeStateStatus` | `CLEAN` or `HAS_HOOKS` | "PR blocked: {value}" |
| `isDraft` | `false` | "PR is a draft" |
| `labels[].name` has `human:review` | absent, for an autonomous merge | "Human-review gate — merge only on explicit same-session user instruction for THIS PR, then remove the label; see pr-standards" |
| `labels.pageInfo.hasNextPage` | `false` | ">100 labels — paginate before trusting the gate. Never read a truncated label list as "`human:review` absent": this gate must fail closed" |
| `reviewDecision` | `APPROVED` or `null` | "Review decision: {value}" |
| `statusCheckRollup.state` | `SUCCESS` | "CI: {state}" |
| All `reviewThreads.isResolved` | `true` | "Unresolved threads" |
| `reviewThreads.pageInfo.hasNextPage` | `false` | ">100 threads — paginate" |

> NOT-ready `mergeStateStatus` values: `BEHIND`, `BLOCKED`, `DIRTY`, `DRAFT`, `UNKNOWN`, `UNSTABLE`.

## Canonical Code-Scanning Alert Count

Replace `<OWNER>`, `<REPO>` before running.

```bash
cscount() {  # $1 = <OWNER>/<REPO>
  local out
  if out=$(gh api "repos/$1/code-scanning/alerts?state=open&per_page=100" \
             --jq 'length' 2>&1); then
    printf '%s\n' "$out"; return 0
  fi
  case "$out" in
    *"must be enabled"*|*"no analysis found"*|*"HTTP 404"*) echo 0; return 0 ;;
    *) printf 'ALERT COUNT UNKNOWN: %s\n' "$out" >&2; return 1 ;;
  esac
}
```

`per_page=100` covers realistic alert counts. Must return `0`; otherwise invoke
`/resolve-codeql fix`.

**Never collapse every error to `0`.** Two very different conditions both return
**HTTP 403**, so branch on the message, not the status code:

| Message | Meaning | Correct result |
| --- | --- | --- |
| `Code Security must be enabled...` | Scanning is off — there are genuinely no alerts | `0` |
| `Resource not accessible by integration` | The token lacks the scope — the query answered nothing | **fail loudly** |

Treating the second as `0` turns a missing permission into a clean bill of
health, which is how an unreviewed PR passes an alert gate.

A 403 here is a credential problem, not a repository problem:

- GitHub App installation tokens need **`security_events: read`**. `checks` and
  `statuses` only expose an analysis's pass/fail rollup, never the alerts.
- Classic PATs need the `security_events` scope (`repo` alone is not enough on
  private repos).

If you cannot get that scope, read the check run instead of guessing — this
reports whether the analysis passed, though not the individual alerts:

```bash
gh pr view <PR_NUMBER> --repo <OWNER>/<REPO> --json statusCheckRollup \
  --jq '[.statusCheckRollup[]? | select(.name|test("CodeQL|Analyze"))
         | "\(.name)=\(.conclusion // .status)"] | join(" ")'
```

Absence of a CodeQL check does not prove code scanning is disabled: it is often
enabled through org-level **default setup**, which adds check runs to the PR
without any workflow file in the repo. Grepping `.github/workflows` for
`codeql.yml` will miss it.

## Canonical Review-Thread Queries

Replace `<OWNER>`, `<REPO>`, `<PR_NUMBER>` using inline literal substitution before running
(uses `--raw-field` — no `-f`/`-F` variable binding).

**Fetch unresolved threads** (`id` = `PRRT_*` node ID for mutations, `databaseId` = numeric ID for REST replies):

```bash
gh api graphql --raw-field 'query=query {
  repository(owner: "<OWNER>", name: "<REPO>") {
    pullRequest(number: <PR_NUMBER>) {
      reviewThreads(first: 100) {
        nodes {
          id isResolved path line startLine
          comments(first: 100) {
            nodes { id databaseId body author { login } createdAt }
          }
        }
      }
    }
  }
}'
```

**Count unresolved** (must equal `0` before merging; checks overflow):

```bash
gh api graphql --raw-field 'query=query {
  repository(owner: "<OWNER>", name: "<REPO>") {
    pullRequest(number: <PR_NUMBER>) {
      reviewThreads(first: 100) { nodes { isResolved } pageInfo { hasNextPage } }
    }
  }
}' --jq '{unresolved: ([.data.repository.pullRequest.reviewThreads.nodes[]
  | select(.isResolved == false)] | length),
  overflow: .data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage}'
```

Must return `{"unresolved": 0, "overflow": false}`. Non-zero `unresolved` or `overflow: true`
means threads remain.

## Review-Thread Mutations

| Operation | Correct | WRONG — do not use |
|---|---|---|
| Reply | `addPullRequestReviewThreadReply` | `addPullRequestReviewComment` (creates new comment, not a reply) |
| Resolve | `resolveReviewThread` | `resolvePullRequestReviewThread` (does not exist) |

Replace `<THREAD_ID>` and `<DATABASE_ID>` before running.

```bash
# Reply via GraphQL (use REST below for markdown/special characters)
gh api graphql --raw-field 'query=mutation {
  addPullRequestReviewThreadReply(
    input: {pullRequestReviewThreadId: "<THREAD_ID>", body: "reply text"}
  ) { comment { id body } }
}'

# Reply via REST (simpler for markdown and special characters)
gh api repos/<OWNER>/<REPO>/pulls/<PR_NUMBER>/comments/<DATABASE_ID>/replies -f body="reply text"

# Resolve
gh api graphql --raw-field 'query=mutation {
  resolveReviewThread(input: {threadId: "<THREAD_ID>"}) { thread { id isResolved } }
}'
```

Failure guide: stale `<THREAD_ID>` → re-fetch threads; permission error → `gh auth status`;
wrong mutation name → check table above.

## Canonical PR Status Summary

Single authoritative format for all PR status output. Reference this section from any
skill that emits a summary — do NOT define local output formats in individual skills.
Title by invocation: `/ship` -> `Ship Summary`; `/finalize-pr` (single/current) ->
`PR Status`; `/finalize-pr all`/`org` -> `Finalization Summary`. Full output
format, emoji mapping, status tags, data queries, "affected repos" definition,
and merge-command shape:
[references/pr-status-summary.md](references/pr-status-summary.md).

## Heredoc Body Pattern

```bash
gh pr edit <PR_NUMBER> --body "$(cat <<'EOF'
body content here
EOF
)"
```

Same pattern for `gh pr create`, `gh pr comment`, `gh issue comment`. Never use `--body-file`.

## Related Skills

- **pr-standards** (github-workflows) — PR creation guards, issue linking

Attribution

dryvistdryvist
View sourceSee grades on GitHubMore from dryvist →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Caveman

Terse caveman voice: answer first, fluff gone, every technical fact kept. Use for /caveman, "caveman mode", "talk like caveman", "be brief", "less tokens". Stays on until "stop caveman" or "normal mode".

1100021 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

698461 votes

Writing Skills

Create and manage Claude Code skills in HASH repository following Anthropic best practices. Use when creating new skills, modifying skill-rules.json, understanding trigger patterns, working with hooks, debugging skill activation, or implementing progressive disclosure. Covers skill structure, YAML frontmatter, trigger types (keywords, intent patterns), UserPromptSubmit hook, and the 500-line rule. Includes validation and debugging with SKILL_DEBUG. Examples include rust-error-stack, cargo-dep...

3931 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3421 votes

catchup

Recovers the conversation and failed tool calls of a previous Codex, Amp, Claude Code, Antigravity, Cline, Copilot CLI, Cursor, DeepSeek Harness, Grok Build, Kimi, OpenCode, Pi Agent, or ZCode session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", asks to recover/summarize a previous session before continuing, or asks to diagnose or report a catchup failure. Do NOT use for the current conversation, git history, or any non-agent log.

741 votes
View all in ai-agents →