Agent-almanac use-graphql-api
git clone https://github.com/pjt222/agent-almanac
T=$(mktemp -d) && git clone --depth=1 https://github.com/pjt222/agent-almanac "$T" && mkdir -p ~/.claude/skills && cp -r "$T/i18n/wenyan-ultra/skills/use-graphql-api" ~/.claude/skills/pjt222-agent-almanac-use-graphql-api-e1165e && rm -rf "$T"
i18n/wenyan-ultra/skills/use-graphql-api/SKILL.mdUse GraphQL API
Discover, construct, execute, and chain GraphQL operations from the command line.
When to Use
- Querying or mutating data via a GraphQL endpoint (GitHub, Hasura, Apollo, etc.)
- Automating GitHub operations that require GraphQL (Discussions, Projects v2)
- Building shell scripts that fetch structured data from GraphQL APIs
- Chaining multiple GraphQL calls where output of one feeds into the next
Inputs
- Required: GraphQL endpoint URL or service name (e.g.,
)github - Required: Operation intent (what data to read or write)
- Optional: Authentication token or method (default:
CLI auth for GitHub)gh - Optional: Output format preference (raw JSON, jq-filtered, variable assignment)
Procedure
Step 1. Discover the Schema
Determine available types, fields, queries, and mutations.
For GitHub:
# List available query fields gh api graphql -f query='{ __schema { queryType { fields { name description } } } }' \ | jq '.data.__schema.queryType.fields[] | {name, description}' # List available mutation fields gh api graphql -f query='{ __schema { mutationType { fields { name description } } } }' \ | jq '.data.__schema.mutationType.fields[] | {name, description}' # Inspect a specific type gh api graphql -f query='{ __type(name: "Repository") { fields { name type { name kind ofType { name } } } } }' | jq '.data.__type.fields[] | {name, type: .type.name // .type.ofType.name}'
For generic endpoints:
# Full introspection query via curl curl -s -X POST https://api.example.com/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{"query":"{ __schema { types { name kind fields { name } } } }"}' \ | jq '.data.__schema.types[] | select(.kind == "OBJECT") | {name, fields: [.fields[].name]}'
Expected: JSON output listing available types, fields, or mutations. The schema response confirms the endpoint is reachable and the auth token is valid.
On failure:
— verify the token; for GitHub, run401 Unauthorizedgh auth status
— the endpoint may disable introspection; consult its documentation insteadCannot query field- Connection refused — verify the endpoint URL and network access
Step 2. Identify the Operation Type
Determine whether your task requires a query (read), mutation (write), or subscription (stream).
| Intent | Operation | Example |
|---|---|---|
| Fetch data | | Get repository details, list discussions |
| Create/update/delete | | Create a discussion, add a comment |
| Real-time updates | | Watch for new issues (rare in CLI) |
For GitHub-specific operations, consult the GitHub GraphQL API docs.
# Quick check: does the mutation exist? gh api graphql -f query='{ __schema { mutationType { fields { name } } } }' \ | jq '.data.__schema.mutationType.fields[].name' | grep -i "discussion"
Expected: Clear identification of whether a query or mutation is needed, plus the exact operation name (e.g.,
createDiscussion, repository).
On failure:
- Operation not found — search with broader terms or check the API version
- Unclear whether query or mutation — if the action changes state, it is a mutation
Step 3. Construct the Operation
Build the GraphQL query or mutation with fields, arguments, and variables.
Query example — fetch a repository's discussion categories:
gh api graphql -f query=' query($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { discussionCategories(first: 10) { nodes { id name } } } } ' -f owner="OWNER" -f repo="REPO" | jq '.data.repository.discussionCategories.nodes'
Mutation example — create a GitHub Discussion:
gh api graphql -f query=' mutation($repoId: ID!, $categoryId: ID!, $title: String!, $body: String!) { createDiscussion(input: { repositoryId: $repoId, categoryId: $categoryId, title: $title, body: $body }) { discussion { url number } } } ' -f repoId="$REPO_ID" -f categoryId="$CAT_ID" \ -f title="My Discussion" -f body="Discussion body here"
Key construction rules:
- Always use variables (
) instead of inline values for reusability$var: Type! - Request only the fields you need to minimize response size
- Use
withfirst: N
for paginated connectionsnodes - Add
to every object selection — you will need it for chainingid
Expected: A syntactically valid GraphQL operation with appropriate variables, field selections, and pagination parameters.
On failure:
- Syntax errors — check bracket matching and trailing commas (GraphQL has no trailing commas)
- Type mismatch — verify variable types against the schema (e.g.,
vsID!
)String! - Missing required fields — add required input fields per the schema
Step 4. Execute via CLI
Run the operation and capture the response.
GitHub — using
:gh api graphql
# Simple query gh api graphql -f query='{ viewer { login } }' # With variables gh api graphql \ -f query='query($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { id name } }' \ -f owner="octocat" -f repo="Hello-World" # With jq post-processing REPO_ID=$(gh api graphql \ -f query='query($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { id } }' \ -f owner="OWNER" -f repo="REPO" \ --jq '.data.repository.id')
Generic endpoint — using curl:
curl -s -X POST "$GRAPHQL_ENDPOINT" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d "$(jq -n \ --arg query 'query { users { id name } }' \ '{query: $query}' )"
Expected: A JSON response with a
data key containing the requested fields, or an errors array if the operation failed.
On failure:
array in response — read the message; common causes are missing permissions, invalid IDs, or rate limitserrors- Empty
— the query matched no records; verify input valuesdata - HTTP 403 — the token lacks the required scope; for GitHub, check
and add scopes withgh auth statusgh auth refresh -s scope
Step 5. Parse the Response
Extract the data you need from the JSON response.
# Extract a single value gh api graphql -f query='{ viewer { login } }' --jq '.data.viewer.login' # Extract from a list gh api graphql -f query=' query($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { issues(first: 5, states: OPEN) { nodes { number title } } } } ' -f owner="OWNER" -f repo="REPO" \ --jq '.data.repository.issues.nodes[] | "\(.number): \(.title)"' # Assign to a variable for later use CATEGORY_ID=$(gh api graphql -f query=' query($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { discussionCategories(first: 20) { nodes { id name } } } } ' -f owner="OWNER" -f repo="REPO" \ --jq '.data.repository.discussionCategories.nodes[] | select(.name == "Show and Tell") | .id')
Expected: Clean, extracted values ready for display or assignment to shell variables.
On failure:
returns null — the field path is wrong; pipe raw JSON tojq
first to inspect structurejq .- Multiple values when expecting one — add a
filter orselect()| first - Unicode issues — add
to jq for raw string output-r
Step 6. Chain Operations
Use output from one operation as input to the next.
# Step A: Get the repository ID REPO_ID=$(gh api graphql \ -f query='query($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { id } }' \ -f owner="$OWNER" -f repo="$REPO" \ --jq '.data.repository.id') # Step B: Get the discussion category ID CAT_ID=$(gh api graphql \ -f query='query($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { discussionCategories(first: 20) { nodes { id name } } } }' \ -f owner="$OWNER" -f repo="$REPO" \ --jq '.data.repository.discussionCategories.nodes[] | select(.name == "Show and Tell") | .id') # Step C: Create the discussion using both IDs RESULT=$(gh api graphql \ -f query='mutation($repoId: ID!, $catId: ID!, $title: String!, $body: String!) { createDiscussion(input: { repositoryId: $repoId, categoryId: $catId, title: $title, body: $body }) { discussion { url number } } }' \ -f repoId="$REPO_ID" -f catId="$CAT_ID" \ -f title="$TITLE" -f body="$BODY" \ --jq '.data.createDiscussion.discussion') echo "Created: $(echo "$RESULT" | jq -r '.url')"
Pattern: Always extract
id fields in earlier queries so they can be passed as ID! variables to subsequent mutations.
Expected: A multi-step workflow where each call succeeds and IDs flow correctly between operations.
On failure:
- Variable is empty — a previous step failed silently; add
and check each intermediate valueset -e - ID format wrong — GitHub node IDs are opaque strings (e.g.,
); never construct them manuallyR_kgDO... - Rate limited — add
between calls or batch queries using aliasessleep 1
Validation
- Introspection query returns schema data (Step 1 succeeds)
- Constructed queries are syntactically valid (no GraphQL parser errors)
- Responses contain
keys withoutdataerrors - Extracted values match expected types (IDs are non-empty strings, counts are numbers)
- Chained operations complete end-to-end (mutation uses IDs from prior queries)
Common Pitfalls
| Pitfall | Prevention |
|---|---|
Forgetting on required variable types | Always check schema for nullability; most input fields are non-null () |
| Using REST IDs in GraphQL | GraphQL uses opaque node IDs; fetch them via GraphQL, not REST |
| Not paginating large result sets | Use / with |
| Hardcoding IDs instead of querying them | IDs differ between environments; always query dynamically |
Ignoring the array | Check for errors even when is present — partial errors are possible |
| Shell quoting issues with nested JSON | Use flag with or pipe through separately |
Related Skills
- scaffold-nextjs-app — scaffolding web apps that consume GraphQL APIs
- create-pull-request — GitHub workflow automation (REST-based counterpart)
- manage-git-branches — Git operations often paired with API automation
- serialize-data-formats — JSON parsing patterns used in response handling