universal/collaboration/gh-cli/SKILL.md
Reliably drive the GitHub gh CLI for issue, PR, and label operations in automation and subagent environments, with pre-flight verification so you never fabricate a success or a fake issue URL.
npx skillsauth add bobmatnyc/claude-mpm-skills gh-cliInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
gh CLIReliable patterns for using the GitHub gh CLI for issue, PR, and label operations — built for automation and subagent environments where silent auth failures are common and fabricating a result is the worst outcome.
When to Use: Any time you create, list, view, edit, or close GitHub issues / PRs / labels through gh, particularly inside a sandboxed shell or a delegated subagent.
When an operation cannot be verified as succeeded, report the failure with the exact error. Never invent an issue number, a URL, or a "created successfully" message. A loud, accurate failure is always better than a fake success — downstream agents and humans act on what gets reported.
Always confirm authentication and repo access first. These are cheap, read-only, and catch the overwhelming majority of failures before you mutate anything.
# 1. Confirm you are authenticated and see which account/scopes are active
gh auth status
# 2. Confirm you can actually reach the target repo (proves token + SSO + network)
gh repo view <org>/<repo> --json name,url
gh auth status reports "not logged into any GitHub hosts" or any error, STOP. Do not run create/edit/close. Diagnose using the sections below, then report the exact error verbatim.gh repo view fails (404, SAML, Bad credentials), STOP. The token may be valid but lack access to this org/repo. Report the exact error.In a subagent: treat a failed pre-flight as a hard stop. Returning "I created issue #42" when auth failed is the failure mode this skill exists to prevent.
gh's OAuth (keyring) tokens are stored in the OS keychain (macOS Keychain, Linux Secret Service). A sandboxed shell cannot read the keychain, so gh falls back to "no credentials found" and reports "not logged into any GitHub hosts" — even though valid credentials exist.
Symptom: gh auth status says not logged in, but the same command works in a normal terminal.
Fix: Run gh with the sandbox disabled so it can reach the keychain.
dangerouslyDisableSandbox: true on the Bash tool call that runs gh.gh auth status) with the sandbox disabled to confirm before proceeding.GH_CONFIG_DIRgh reads its config (including which hosts.yml holds credentials) from GH_CONFIG_DIR if that env var is set, otherwise from ~/.config/gh. A stale or wrong GH_CONFIG_DIR pointing at a nonexistent directory makes gh report "not logged in" even when valid keyring credentials exist elsewhere.
Diagnose:
echo "$GH_CONFIG_DIR" # Is it set? To what?
ls "$GH_CONFIG_DIR" # Does the directory exist? Has hosts.yml?
Find the config dir that actually has credentials:
find ~ -maxdepth 5 -name hosts.yml -path '*gh*'
Fix — point GH_CONFIG_DIR at the real config dir, or unset it to fall back to the default:
# Option A: point at the working config explicitly
GH_CONFIG_DIR=/path/to/real/gh-config gh auth status
# Option B: unset to use ~/.config/gh
unset GH_CONFIG_DIR && gh auth status
gh auth status may list several accounts, with one marked active. gh uses the active account. If the active one lacks access to your target org/repo, switch:
gh auth status # see all accounts; note which is "Active account: true"
gh auth switch -u <username> # make the correct account active
gh repo view <org>/<repo> --json name,url # re-verify after switching
Personal Access Tokens pulled from .env files are frequently rejected by orgs that enforce SAML SSO:
HTTP 403: Resource protected by organization SAML enforcement.
You must grant your Personal Access token access to this organization.
Why: Even a valid PAT must be SSO-authorized for that specific org via a browser grant. Tokens injected from .env in a headless environment usually have never been through that grant.
Preferred fix: Use a keyring OAuth token (gh auth login) that has already been SSO-authorized for the org — OAuth logins prompt for the SSO grant interactively. To authorize an existing PAT, complete the org's SSO browser grant in GitHub settings (Developer settings → PATs → Configure SSO).
Rule of thumb: For SSO-enforced orgs, prefer keyring OAuth over .env PATs.
When local auth is unavailable, unreliable, or you need robust bulk operations, run gh from a server that holds a GitHub App installation token. App installation tokens are SSO-exempt and scoped to the installation, making them the most reliable path for automation.
A common pattern is to dispatch the command to a server via AWS SSM:
aws ssm send-command \
--instance-ids <instance-id> --region <region> \
--document-name "AWS-RunShellScript" \
--parameters '{"commands":["gh issue create --repo <org>/<repo> --title \"...\" --body \"...\""]}'
Treat this as the robust path for automation / CI when keychain-backed local auth cannot be guaranteed.
Use --json <fields> --jq <filter> to get machine-parseable output and to confirm results rather than trusting exit codes alone.
# Create — capture the returned URL; do not invent it
gh issue create --repo <org>/<repo> --title "Title" --body "Body" --label bug
# List (parseable)
gh issue list --repo <org>/<repo> --state open --json number,title,url --jq '.[]'
# View a specific issue
gh issue view <number> --repo <org>/<repo> --json number,title,state,url
# Close
gh issue close <number> --repo <org>/<repo> --comment "Resolved"
# Edit (also the basis of upsert-by-title — see below)
gh issue edit <number> --repo <org>/<repo> --add-label triaged --body "Updated body"
Avoid duplicate issues by checking for an existing open issue with the same title before creating:
existing=$(gh issue list --repo <org>/<repo> --state open \
--search "in:title \"My exact title\"" \
--json number,title --jq '.[] | select(.title=="My exact title") | .number' | head -n1)
if [ -n "$existing" ]; then
gh issue edit "$existing" --repo <org>/<repo> --body "Refreshed body"
else
gh issue create --repo <org>/<repo> --title "My exact title" --body "..."
fi
gh pr view <number> --repo <org>/<repo> --json number,title,state,url,mergeable
gh pr list --repo <org>/<repo> --state open --json number,title,headRefName --jq '.[]'
gh label create "code-intelligence" --repo <org>/<repo> --color BFD4F2 --description "..."
gh label list --repo <org>/<repo> --json name,color --jq '.[].name'
This section documents a concrete, worked example of the failure modes above — diagnosed while ticketing subagents could not create issues in duettoresearch/code-intelligence. The generic instructions above are what you apply; this is the case study showing how they combine in practice.
Ticketing subagents reported "not logged into any GitHub hosts" and "Bad credentials" and could not create issues in duettoresearch/code-intelligence. Some runs would otherwise have been tempted to report a fabricated issue URL — do not.
GH_CONFIG_DIR — subagents inherited GH_CONFIG_DIR=/Users/masa/.config/gh-duetto, a directory that does not exist, so gh saw no credentials. (See §3.).env.local PAT blocked by SAML SSO — the PAT in .env.local is rejected by duettoresearch org SAML enforcement. (See §5.)bobmatnyc returned HTTP 401 Bad credentials. (See §4 — wrong active account.)GH_CONFIG_DIR=/Users/masa/.config/gh-personal # real config dir with valid hosts.yml
sandbox: disabled # so gh can read the keychain
active account: bob-duetto # scopes: gist, read:org, repo, workflow
result: gh repo view duettoresearch/code-intelligence → succeeds
# Run gh with the real config dir AND the sandbox disabled
# (in Claude Code: dangerouslyDisableSandbox: true on the Bash call)
GH_CONFIG_DIR=/Users/masa/.config/gh-personal gh auth status
# If the wrong account is active, switch first:
GH_CONFIG_DIR=/Users/masa/.config/gh-personal gh auth switch -u bob-duetto
# Then re-verify before any mutation:
GH_CONFIG_DIR=/Users/masa/.config/gh-personal gh repo view duettoresearch/code-intelligence --json name,url
Only after that pre-flight succeeds should the subagent run gh issue create etc. — and it must report the real returned URL, never a fabricated one.
gh issue create ... # exit code ignored
# "Created issue #42!" ← fabricated; the command may have failed silently
url=$(gh issue create --repo <org>/<repo> --title "T" --body "B")
echo "Created: $url" # report the actual returned URL, or the actual error
It usually means sandbox/keychain or a stale GH_CONFIG_DIR — diagnose first (§2, §3).
.env PATs for SSO-enforced orgsPrefer keyring OAuth or a server-side App installation token (§5, §6).
gh auth status + gh repo view before any op.GH_CONFIG_DIR, multiple accounts, SAML SSO PAT rejection.development
Optimize web performance using Core Web Vitals, modern patterns (View Transitions, Speculation Rules), and framework-specific techniques
development
Best practices for documenting APIs and code interfaces, eliminating redundant documentation guidance per agent.
development
Comprehensive API design patterns covering REST, GraphQL, gRPC, versioning, authentication, and modern API best practices
development
Visual verification workflow for UI changes to accelerate code review and catch ...