ghactions, one workflow piece per page
Twenty-nine pieces for your first CI: why pushed workflows silently refuse to run, the .github/workflows file itself, push and pull_request triggers and what each one sees, branch filters, cron in UTC on the default branch, the manual run button, forks opting in, jobs and steps and needs ordering, runs-on machines, checkout and run and uses, secrets and the GITHUB_TOKEN dial, cache keys, artifacts between machines, the matrix, if conditions, concurrency cancels, timeouts, contexts, environments, re-running failed jobs, and the six-question checklist for the run that never started.
A diagram, the mechanism, and one thing to do this week. That's a page.
ghactions, one workflow piece per page
Twenty-nine pieces for your first CI: why pushed workflows silently refuse to run, the .github/workflows file itself, push and pull_request triggers and what each one sees, branch filters, cron in UTC on the default branch, the manual run button, forks opting in, jobs and steps and needs ordering, runs-on machines, checkout and run and uses, secrets and the GITHUB_TOKEN dial, cache keys, artifacts between machines, the matrix, if conditions, concurrency cancels, timeouts, contexts, environments, re-running failed jobs, and the six-question checklist for the run that never started.
Set in Space Grotesk, Inter and JetBrains Mono (SIL Open Font License).
General information only. Every fact in this book is as the official GitHub documentation states it, fetched and read during this build: events that trigger workflows and workflow syntax for GitHub Actions (docs.github.com/en/actions), including the push, pull_request, schedule and workflow_dispatch events, the jobs, steps, uses, run, needs, if, matrix, concurrency, permissions and timeout-minutes keys; plus the GitHub docs on variables and secrets, caching with actions/cache, artifacts, and using environments for deployment. GitHub Actions evolves: check the current documentation for behaviour that matters to you. Demand evidence from live beginner threads, reconfirmed at dispatch; no facts are sourced from Reddit. An independent guide, not affiliated with or endorsed by GitHub or Microsoft.
Your purchase is for personal use only. You do not have redistribution rights: please do not share, resell, or republish this book or its pages.
© 2026 Steve Hodgkiss. All rights reserved. Personal use only; no redistribution rights.
Edition 1.0 · stevehodgkiss.net
Contents
Why didn't it run
The question every beginner arrives with: a pushed workflow that never started. The file's home and shape, the push trigger, branch filters, cron's default-branch rule, the manual button, forks opting in.
- 01One YAML file
- 02The push trigger
- 03Cron needs main
- 04Forks opt in
Per GitHub Docs, Workflow syntax for GitHub Actions: workflow files use YAML and must have a .yml or .yaml extension; they must be stored in the .github/workflows directory of the repository. The workflow name shows under the Actions tab; if name is omitted, GitHub displays the workflow file path relative to the repository root.
One YAML file
A workflow is one YAML file in one specific place: .github/workflows/ at the repository root, with a .yml or .yaml extension. Nothing outside that folder ever runs. The tab and spaces rule bites here: YAML forbids tabs for indentation, so spaces only.
Give it a name: too. Omit it and the Actions tab shows the file path instead of a friendly title, which is why so many beginners see a filename where a workflow name should be.
One folder, one file, one name: that is the whole unit.
Create .github/workflows/ci.yml in one of your repositories today with only name: CI at the top. Commit it to the default branch and find it listed under the Actions tab, showing no runs yet.
Per GitHub Docs, Events that trigger workflows (push entry): the push event runs your workflow when you push a commit or tag, or when you create a repository from a template; the SHA in the run is the tip commit pushed to the ref. The classic beginner failure, per the recurring r/github threads at dispatch: a workflow file that exists only on an unpushed or non-triggering branch never fires.
The push trigger
on: push runs the workflow every time a commit is pushed to any branch. It is the default heartbeat of CI, and it answers the most common beginner question, why did nothing run, with a checklist: the file must be committed and pushed, and it must sit on the branch that was pushed. A workflow file that only exists on your laptop does nothing.
The run uses the pushed commit as its snapshot, so what the workflow tests is exactly what you pushed.
Push trigger, pushed file. No push, no run.
Add on: push to your ci.yml, commit a trivial change, and watch the Actions tab start a run the moment the push lands. If nothing starts, run git status before touching the YAML.
Per GitHub Docs, Events that trigger workflows (schedule): scheduled workflows run on the latest commit on the default branch only; POSIX cron syntax in UTC by default; the shortest interval is five minutes; high load times, especially at the start of the hour, can delay scheduled runs. Per the dispatch r/github threads: cron jobs that only exist on a feature branch never fire, by design.
Cron needs main
schedule: with a cron line is the timed trigger, and it carries two non-negotiable rules: the cron is UTC, and the scheduled workflow runs only on the latest commit of the default branch. A cron job merged into a feature branch never fires, and that is by design.
Two habits: avoid the top of the hour, when everyone's jobs queue at once and runs start late; and remember 17 5 * * 1 means 05:17 UTC on Mondays, not your local Monday.
Cron reads UTC, lives on main, and waits in queues like everything else.
Convert a time you actually want a job to run, say 9am your local time, into UTC and write the cron line for it. Check it against your clock's timezone offset and park the line in a note for the schedule page's build.
Per GitHub Docs (usage and billing, and the dispatch threads on forked repos): when you first visit the Actions tab of a fork, GitHub asks you to enable workflows before any run starts; the classic dispatch thread pattern is a pushed project whose build silently never starts until the Actions tab is visited and workflows enabled.
Forks opt in
Fork a repository and GitHub deliberately runs nothing: the Actions tab shows an enable-workflows confirmation, and until you click it, every push sits silent. The project's build not starting after forking is almost always this, not your YAML.
Visit the tab, read the prompt, enable, and the next push behaves normally. It exists so a fork does not burn compute or run untrusted configurations the moment it is copied.
Forked? The Actions tab is your first stop, not your last resort.
If you have a fork whose CI has never once run, open its Actions tab today and either enable workflows or confirm the prompt is gone. Note the exact wording of the prompt while you are there.
Per GitHub Docs, Workflow syntax (jobs): a workflow run is made up of one or more jobs, each of which runs on a fresh runner virtual machine by default and in parallel with the others unless needs creates dependency; each job has steps that can run shell commands or actions.
Jobs and steps
Under jobs, each job id names a block that gets its own fresh virtual machine. Jobs run in parallel by default. Inside a job, steps run in order on that one machine, sharing files through the workspace.
Two jobs mean two clean machines that see nothing of each other; two steps mean one machine doing two things back to back. Choose by what needs to share files, not by what looks organised.
Job: a machine. Step: a line on that machine.
Split your ci.yml into a job with two steps: the first prints the runner's operating system, the second lists the workspace files. Confirm both steps share one machine by matching the workspace listing.
Per GitHub Docs (using secrets in GitHub Actions and contexts guidance): secrets are stored at repository or organization level, referenced with the secrets context, exposed to steps as environment variables; pull requests from forks do not get secrets; the GITHUB_TOKEN is automatically available.
Secrets, masked
Anything a step needs that must not be printed, an API key, a deploy token, lives as a secret under the repository's settings, and reaches steps through the secrets context into an environment variable. Values that match a secret appear as stars in every log.
The boundary: pull requests from forks receive no secrets, so an outside contribution cannot print your keys. A step that needs secrets and must run on fork PRs is a design error, not a YAML one.
Secrets go in settings, not files; forks get nothing.
Add one real secret to a repository you own today, print it in a step, and confirm the log shows stars. Then delete the test step; the habit is knowing the mask works.
Composite of the dispatch threads' recurring checklist: the workflow file must be pushed, on the triggering branch, workflows enabled on forks, cron on the default branch in UTC; plus per GitHub Docs, workflow_dispatch for manual testing. This page is the whole book as one checklist.
The run checklist
Almost every silent workflow reduces to six questions. Was the file pushed at all? Is it on the branch that triggers? Is cron on the default branch? Are workflows enabled in a fork? Does the branch filter actually match? Is the event the one you think fired?
Work down the list before touching the YAML. In the threads that produced this book, the answer was on this page nearly every time, and the workflow was fine all along.
Six questions, in order. That is the whole checklist.
Bookmark or screenshot this checklist and use it on the next silent run you meet. If all six pass and nothing runs, then, and only then, open the YAML.