GitHub CLI, REST/GraphQL APIs, Workflow Dispatch, and Automation Control: Core Concepts and Mental Model
Model GitHub Actions control as identity-bound, versioned API requests against exact workflow/run resources, followed by asynchronous state verification rather than “send a command and assume success.”
Learning objectives
- Explain the difference between an operator identity, an API request, a GitHub resource and the asynchronous state change that follows.
- Track repository, workflow ID/path, run ID/attempt, API version, request method/body, response status and final run state independently.
- Use GitHub CLI as an authenticated client without confusing CLI convenience with the underlying REST or GraphQL contract.
- Explain REST pagination, GraphQL cursors, primary/secondary rate limits and why blind retries of mutations are unsafe.
- Choose read-only inspection before dispatch, cancel, rerun or deletion and preserve first-failure evidence before changing run state.
1. The practical problem: automation control is a distributed-state problem
Chapter 30 gave you run IDs, attempts, logs and evidence. Chapter 31
turns those identifiers into control-plane inputs. The hard part is
not typing gh run rerun; it is proving that the command
is authenticated as the intended actor, points at the intended
repository/workflow/run, uses a supported API contract, and observes
the final asynchronous state instead of treating an accepted request
as success.
A dangerous controller says “cancel the latest failed run.” A reliable controller says “for repository R, workflow resource W and lab request K, mutate run ID N only after read-back proves all guards; then poll N until the server reports a terminal state.” That difference is the chapter goal: exact identity and bounded state transitions rather than convenience-driven mutation.
2. Mental model: identity → request → resource → asynchronous transition → proof
Start with an operator or automation identity. GitHub CLI, REST and GraphQL merely carry requests made by that identity. A request addresses a repository/workflow/run resource. Some requests are read-only; others ask GitHub to dispatch, cancel or rerun work. Those mutating calls change control-plane state asynchronously. A final GET, a bounded poll or a webhook proves what actually happened.
flowchart TD
A[Operator or automation identity] --> B[gh / REST / GraphQL request]
B --> C[Repository + workflow/run resource]
C --> D{Read or mutate?}
D -->|Read| E[Metadata / pagination / rate-limit evidence]
D -->|Dispatch cancel rerun| F[Request accepted or rejected]
F --> G[Queued / in progress / completed state]
G --> H[Bounded polling or webhook]
H --> I[Verified terminal state + preserved evidence]
The arrow from request acceptance to final state is deliberately explicit. A dispatch response is not a green workflow; a cancellation response is not immediate process death; a rerun request is not a new source revision. You must observe each transition separately.
3. State ledger: record the nouns before issuing verbs
| State | Example evidence | Why it matters |
|---|---|---|
| Authentication identity |
gh auth status,
gh api user --jq .login
|
Proves who is asking; never record the raw token. |
| Repository | owner/repo, repository ID/visibility |
Prevents cross-repository mutations. |
| Workflow | file path + numeric workflow ID + state | A path is human-readable; the numeric ID identifies the current workflow resource. |
| Run | run ID + attempt + event + head SHA + display title | The primary guard for cancellation/rerun and evidence correlation. |
| REST contract |
X-GitHub-Api-Version: 2026-03-10 + Accept
header
|
Pins breaking API semantics instead of inheriting defaults silently. |
| Request | HTTP method + endpoint + bounded JSON body | Shows which state change was actually requested. |
| Response | status + returned identifiers | Acceptance/rejection evidence, not final execution evidence. |
| Pagination/rate budget | Link/cursor + remaining/reset/retry-after | Prevents incomplete inventory and abusive retry loops. |
| Final state | run status/conclusion + attempt + jobs/artifacts | Proves the asynchronous operation completed as intended. |
4. Authentication and authorization are separate from command syntax
GitHub CLI can authenticate interactively, from environment-provided
credentials, or through an automation identity. The token itself is
secret material; the safe evidence is the actor identity, repository
boundary and permission model. For long-lived organization
automation, GitHub recommends a GitHub App rather than a personal
token. For a disposable human-operated lab, normal
gh auth login is sufficient.
Current fine-grained token rules are explicit: reading workflow runs
requires repository permission Actions: read; dispatch,
cancel and rerun endpoints require repository permission
Actions: write. A classic PAT typically uses broad
repo scope for private repositories, which is why
fine-grained tokens or GitHub Apps are preferable when automation
must be least-privileged. Inside a workflow,
GITHUB_TOKEN is a different short-lived installation
token whose permissions are declared by workflow/job configuration.
5. Workflow path, workflow ID, run ID and attempt are not interchangeable
The workflow file name such as control-target.yml is
convenient for humans and accepted by many REST/CLI workflow
endpoints. The numeric workflow ID is stable for the lifetime of
that workflow resource and survives display-name changes, but
deleting/recreating a workflow can create a new resource ID.
Production controllers should resolve and record both path and ID
before a mutation.
A run ID identifies a workflow-run resource. A rerun keeps the same
run ID and source ref/SHA but increments run_attempt. A
browser job URL number is not necessarily the database job ID
required by gh run rerun --job; obtain the real job
databaseId from
gh run view RUN_ID --json jobs.
6. REST versioning and the 2026 dispatch response
GitHub REST is versioned. As verified September 10, 2026,
2026-03-10 is the current API version; requests that
omit the version header still default to 2022-11-28.
This course pins 2026-03-10 explicitly so a controller
does not change behavior merely because GitHub changes a default or
retires an older contract.
The current workflow-dispatch endpoint is especially important:
POST
/repos/OWNER/REPO/actions/workflows/WORKFLOW_ID/dispatches
now returns HTTP 200 with
workflow_run_id, run_url and
html_url. Older automation often had to infer the new
run from a later list call. New controllers should consume the
returned run ID directly whenever the response is received.
Compatibility note: documentation and API behavior are continuously delivered. If your GitHub Enterprise Server or an older supported REST version behaves differently, record that platform/version and use the documented contract for that platform rather than assuming GitHub.com 2026 behavior.
7. GitHub CLI is a client, not a separate source of truth
Current GitHub CLI release history shows gh 2.100.0 as
the latest release on September 10, 2026. The labs record
gh --version but use stable command families:
gh workflow for workflow resources,
gh run for run resources, and gh api when
exact REST headers, endpoints, status semantics or GraphQL queries
matter.
# Read-only local/client state
gh --version
gh auth status
gh api user --jq '.login'
# Read-only workflow/run inventories
gh workflow list --all --json id,name,path,state
gh run list --limit 10 --json databaseId,attempt,event,headSha,status,conclusion,url
# Explicit REST contract
gh api \
-H 'Accept: application/vnd.github+json' \
-H 'X-GitHub-Api-Version: 2026-03-10' \
repos/{owner}/{repo}/actions/workflows
8. Pagination is correctness, not UI polish
Collection endpoints intentionally return pages. A script that
inspects only page 1 can miss the resource it is supposed to protect
or mutate. REST responses use a Link header when more
pages exist; gh api --paginate follows pages for
supported endpoints. GraphQL uses connection cursors and
pageInfo; the query must request
hasNextPage/endCursor and feed the cursor
into the next request.
# REST: let gh follow every page, then select only fields you need.
gh api --paginate -H 'Accept: application/vnd.github+json' -H 'X-GitHub-Api-Version: 2026-03-10' 'repos/{owner}/{repo}/actions/runs?per_page=100' --jq '.workflow_runs[] | [.id,.run_attempt,.status,.conclusion] | @tsv'
9. Rate limits shape controller design
Authenticated REST requests have a primary budget, and mutative
requests also participate in secondary abuse limits. The built-in
GITHUB_TOKEN currently has a REST budget of 1,000
requests per hour per repository on ordinary GitHub.com
repositories; GraphQL has its own point budget. These values are
operational assumptions, not business logic—read the response
headers or rate-limit endpoint instead of hard-coding an infinite
polling loop.
When a response is rate-limited, honor retry-after and
x-ratelimit-reset. More importantly, do not treat a
timeout after a POST as proof that the POST failed. A mutation may
have reached GitHub even if your client never received the response.
Reconcile state before retrying.
10. Idempotency means making ambiguous retries safe
Dispatch, cancel and rerun are not generic “set state to X”
operations with a universal client-supplied idempotency key. The
safe pattern is a controller-owned request ID, an exact returned run
ID when available, a local control ledger, and read-before-write
guards. Put the synthetic request ID into run-name or a
validated workflow input so you can reconcile an ambiguous transport
failure without selecting an unrelated run.
A retry decision therefore depends on evidence: if the dispatch response returned a run ID, never dispatch again merely because polling is slow. If the network failed before a response arrived, search only the exact workflow/ref/time/request-ID window; if a matching run exists, adopt that run ID. If evidence remains ambiguous, stop and require operator review rather than creating duplicate side effects.
11. GraphQL adds selective topology and cursor queries—not a reason to avoid REST mutations
GitHub now exposes Actions Workflow and
WorkflowRun GraphQL objects, including workflow run
connections and runAttempt. That is useful when one
query should fetch a deliberately shaped view of workflows/runs
together with other GraphQL-native repository data. REST remains
clearer for the documented dispatch/cancel/rerun endpoints used in
this chapter.
GraphQL also has its own point-based rate limit and node/query
limits. Request only the fields you need and paginate connections. A
200 HTTP response can still contain GraphQL errors, so
a controller must inspect the payload rather than equating HTTP 200
with a complete successful query.
12. Read-only inspection before any mutation
# Exact repository/workflow state. No mutation.
gh repo view --json nameWithOwner,url,visibility
gh workflow list --all --json id,name,path,state
gh api \
-H 'Accept: application/vnd.github+json' \
-H 'X-GitHub-Api-Version: 2026-03-10' \
repos/{owner}/{repo}/actions/workflows/control-target.yml
gh api \
-H 'Accept: application/vnd.github+json' \
-H 'X-GitHub-Api-Version: 2026-03-10' \
rate_limit --jq '.resources.core'
Only after this ledger is captured should the controller issue a
dispatch, cancel or rerun. Do not print Authorization headers or run
curl -v with a bearer token simply to prove
authentication.
13. Lesson summary
GitHub Actions control is safe when requests are identity-bound,
resource-bound, version-bound and evidence-bound.
gh makes the interface convenient; REST defines
explicit mutations and status semantics; GraphQL provides selective
connected reads. None of them removes the need for pagination,
rate-limit handling, exact run guards or final-state verification.
Knowledge check
Why is “cancel the latest run” unsafe automation?
“Latest” is a moving query result, not an ownership proof. Resolve and read back the exact run ID plus workflow/event/request guards before mutation.
What does a successful workflow-dispatch REST response prove?
It proves GitHub accepted the dispatch and, in the current 2026-03-10 contract, gives the exact created run ID/URLs. It does not prove the run has started or succeeded.
Why record both workflow path and workflow ID?
The path is human/audit context and the ID identifies the current workflow resource; either can change under rename/delete/recreate scenarios.
When is GraphQL useful here?
For selective connected reads and cursor-based workflow/run topology. The chapter still uses REST for explicit Actions control mutations.
A POST times out. Should the controller immediately retry?
No. The server may already have accepted it. Reconcile exact state/request identity first, then retry only if evidence proves no side effect occurred.
Official references and version notes
- GitHub REST API versions — Current supported REST versions and X-GitHub-Api-Version behavior; examples pin 2026-03-10.
- REST: workflows — Workflow discovery and current workflow_dispatch endpoint, permissions, inputs and response schema.
- REST: workflow runs — Exact run read, cancel, rerun, failed-job rerun and logs endpoints.
- REST: workflow jobs — Exact workflow-job identity and job rerun/log APIs.
- REST pagination — Link headers, per_page and reliable traversal of paginated collections.
- REST rate limits — Primary/secondary rate-limit behavior and retry guidance.
- GraphQL Actions schema — Workflow and WorkflowRun objects, runAttempt and cursor-based run connections.
- GraphQL rate/query limits — Point budgets, cursor constraints and query limits.
- GitHub CLI: gh api — Authenticated REST/GraphQL requests, headers, --paginate, --slurp and JSON selection.
- GitHub CLI: workflow run — Manual workflow dispatch through gh and supported input/ref forms.
- GitHub CLI: run list — Run listing and exact filters/JSON fields.
- GitHub CLI: run view — Attempt-aware run inspection, jobs and logs.
- GitHub CLI: run watch — Run watching and current fine-grained PAT limitation.
- GitHub CLI: run rerun — Full/failed/specific-job reruns and the job databaseId requirement.
- GitHub CLI releases — Current GitHub CLI release history; lab assumption recorded as 2.100.0 on 2026-09-10.
- Personal access tokens — Fine-grained token preference and guidance to use GitHub Apps for long-lived organization automation.
- actions/upload-artifact v7.0.1 — Full-SHA pin used to retain tiny run evidence in the disposable target workflow.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.