Chapter 31Lesson 01~220 minutes

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.”

GitHub CLIREST APIGraphQLWorkflow controlResource identity

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.

GitHub Actions control plane
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.

Next lesson

GitHub CLI, REST/GraphQL APIs, Workflow Dispatch, and Automation Control: Guided Hands-On Workflow

Continue with the next lesson to build on the current concepts, evidence, security boundaries, and operational practices.

Knowledge check

Why is “cancel the latest run” unsafe automation?

What does a successful workflow-dispatch REST response prove?

Why record both workflow path and workflow ID?

When is GraphQL useful here?

A POST times out. Should the controller immediately retry?

Official references and version notes

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.