GitHub
April 28, 2026 · View on GitHub
Shared reference for the gh CLI and gh api / gh api graphql
invocations the skills use against the project's tracker repository.
The skills reference this file for the recipe shape; each inline
command in a skill already substitutes the tracker repo slug from the
adopting project's manifest (see
../../<project-config>/project.md).
Placeholder convention used below:
<tracker>— the tracker repository slug from<project manifest>.tracker_repo(for Airflow,<tracker>).<upstream>— the upstream codebase slug from<project manifest>.upstream_repo(for Airflow,<upstream>).<N>— issue or PR number.
Authentication
Every skill's Step 0 pre-flight must verify that gh is authenticated
and has collaborator access to <tracker>:
gh auth status # must show logged-in user + scopes
gh api repos/<tracker> --jq .name # must return the repo name; 401/403/404 means stop
A non-zero exit on either command is a hard stop — the skill reports
the failure and asks the user to gh auth login (or to ask for
collaborator access to the tracker) rather than retrying.
Collaborator lookup (security-team roster)
gh api repos/<tracker>/collaborators --jq '.[].login'
The authoritative "who is on the security team" list. Every
collaborator counts regardless of permission level (read / triage /
write / maintain / admin). Roster snapshots maintained in the project
manifest files (for Airflow, release-trains.md)
are caches of this command's output and can drift between changes.
Issues
Read
gh issue view <N> --repo <tracker> \
--json number,title,state,body,labels,milestone,assignees,author
Add --json comments when the skill needs the comment trail, and
--json projectItems when it needs to see which project boards the
issue sits on.
Create
gh issue create --repo <tracker> \
--title '<title>' \
--body-file <path> \
--label '<label-1>' --label '<label-2>'
Always write the body to a temp file and pass --body-file — shell
quoting silently corrupts anything with literal backticks, $(…), or
newlines inside a multi-paragraph body.
Edit — labels
gh issue edit <N> --repo <tracker> \
--add-label '<label-a>,<label-b>' \
--remove-label '<label-c>'
Apply every add + remove in one call so the change lands as a single audit-trail entry rather than as N separate events.
Edit — assignees
gh issue edit <N> --repo <tracker> --add-assignee @me # self-assign
gh issue edit <N> --repo <tracker> --add-assignee <handle> # named user
Edit — body
gh issue edit <N> --repo <tracker> --body-file <tmpfile>
Write the edited body to a temp file first. The skills that perform
"body-field surgery" (updating one ### <field> section without
touching the rest) read the full body, replace the targeted section
between its header and the next ### heading, and write the result
back via --body-file.
Comment
gh issue comment <N> --repo <tracker> --body-file <tmpfile>
Before posting, scrub the comment body for bare-name mentions of
project maintainers / release managers / security-team members and
replace with @-handles. See the per-project mention rule (for
Airflow, ../../<project-config>/naming-conventions.md#mentioning-airflow-maintainers-and-security-team-members)
for the grep-list of names to check.
Close / reopen
gh issue close <N> --repo <tracker> --reason completed # or 'not planned'
gh issue reopen <N> --repo <tracker>
Milestones
List
gh api 'repos/<tracker>/milestones?state=all&per_page=100' \
--jq '.[] | select(.title == "<target>") | {number, state}'
Create
gh api repos/<tracker>/milestones \
-f title='<target>' \
-f state=open \
-f description='<optional one-line description>'
The create call returns the milestone object including its number —
capture that in case the milestone is later closed (see fallback
below).
Assign to an issue
gh issue edit <N> --repo <tracker> --milestone '<title>'
Closed-milestone fallback. gh issue edit --milestone '<title>'
fails with '<title>' not found if the milestone is closed. Fall back
to the REST API and reference it by number:
gh api repos/<tracker>/issues/<N> -X PATCH -F milestone=<number>
Labels
List
gh label list --repo <tracker> --limit 100 \
--json name,description,color --jq '.[].name'
Create
gh label create '<name>' --repo <tracker> \
--description '<short description>' \
--color '<hex>'
Do not silently create labels without asking the user. Label names are the shared vocabulary of the security team, and new labels should be discussed.
Pull requests
Create (public PR on the upstream repo)
gh pr create --web --repo <upstream> \
--base <base-branch> --head <user>:<branch> \
--title "<neutral title>" \
--body "$(cat <path-to-body>)"
--web is load-bearing. Per the per-project convention (for Airflow,
see
../../<project-config>/fix-workflow.md#pr-creation-convention),
always open PRs through the browser so the human reviewer can check
the title, body, and Gen-AI disclosure before clicking Create.
Edit — backport / other labels
gh pr edit <N> --repo <upstream> --add-label '<backport-label>'
Safe to run immediately after PR creation; the backport bot acts on the label when the PR merges, not when it is applied.
Cross-link from the public PR back to the private tracker
Forbidden. The public PR body and any follow-up public comment must
not reveal the CVE, the security nature, or the private tracker URL.
Enforce via the scrub step before writing the PR body — see the
per-project scrubbing rule (for Airflow,
../../<project-config>/fix-workflow.md#pr-title--body-scrubbing).
GraphQL (Projects V2)
See project-board.md for the board
introspection and updateProjectV2ItemFieldValue patterns.
Error handling
If any state-changing command fails, stop the apply loop, report the failure verbatim, and ask the user how to proceed — do not guess. Most sync-style skills order their apply list so the load-bearing edit (usually the body edit) is first; a failure on a later step leaves the body correct and a subsequent sync run will catch up the rest.