{"id":"l-git-4-4","courseId":"git-github","lang":"en","kind":"lesson","summary":"Add a GitHub Actions workflow that checks every pull request, then protect main with a ruleset so changes can only land through a pull request whose checks pass.","html":"<p>Everything so far has relied on you remembering. Remember to run the HTML check, to open a pull request rather than pushing to <code dir=\"ltr\">main</code>, to never force-push. People forget, especially at 11 p.m. before a job application. This lesson moves those habits out of your head and into GitHub: a machine runs the checks on every pull request, and rules make <code dir=\"ltr\">main</code> impossible to change any other way.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Give the portfolio something to check</h2>\n<p>Continuous integration needs a command that fails when something is wrong. For an HTML site, a validator is a good start. In your portfolio, on a new branch:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> switch</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -c</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> add-ci</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">npm</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> init</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -y</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">npm</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> install</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --save-dev</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> html-validate</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">npm</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> pkg</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> set</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> scripts.test=\"html-validate index.html projects.html\"</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">echo</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '{ \"extends\": [\"html-validate:recommended\"] }'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ></span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> .htmlvalidate.json</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">npm</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> test</span></span></code></pre></div>\n<p><code dir=\"ltr\">node_modules/</code> is already in your <code dir=\"ltr\">.gitignore</code> from two lessons ago; commit <code dir=\"ltr\">package.json</code>, <code dir=\"ltr\">package-lock.json</code> and <code dir=\"ltr\">.htmlvalidate.json</code>. If <code dir=\"ltr\">npm test</code> reports problems, that’s the point: fix them, or note them for the first CI run. On a fresh portfolio it often finds this:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"text\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Text</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code>index.html\n  1:1  error  DOCTYPE should be uppercase  doctype-style</code></pre></div>\n<h2 id=\"sec-2\" tabindex=\"-1\">Your first workflow</h2>\n<p><strong>GitHub Actions</strong> runs workflows defined in YAML files under <code dir=\"ltr\">.github/workflows/</code>. Create one:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">.github/workflows/ci.yml</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">CI</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">on</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  pull_request</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    branches</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">main</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  push</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    branches</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">main</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">jobs</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  check</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    runs-on</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">ubuntu-latest</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    steps</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">uses</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">actions/checkout@v7</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">uses</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">actions/setup-node@v7</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">        with</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          node-version</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">24</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          cache</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">npm</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">run</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">npm ci</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">run</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">npm test</span></span></code></pre></div>\n<p>Read it top to bottom:</p>\n<ul>\n<li><code dir=\"ltr\">on</code> lists the <strong>events</strong> that start the workflow: any pull request into <code dir=\"ltr\">main</code> (opened, or updated with new commits), and pushes to <code dir=\"ltr\">main</code> after merging.</li>\n<li><code dir=\"ltr\">jobs</code> holds one job, <code dir=\"ltr\">check</code>. That id becomes the name of the status check GitHub shows on the pull request.</li>\n<li><code dir=\"ltr\">runs-on</code> picks a <strong>runner</strong>, a fresh virtual machine GitHub provides. It starts empty every time.</li>\n<li><code dir=\"ltr\">steps</code> run in order. <code dir=\"ltr\">uses</code> runs a published <strong>action</strong>: <code dir=\"ltr\">actions/checkout</code> copies your repository onto the runner, <code dir=\"ltr\">actions/setup-node</code> installs Node.js 24 and caches npm downloads. <code dir=\"ltr\">run</code> executes a shell command: <code dir=\"ltr\">npm ci</code> installs exactly what <code dir=\"ltr\">package-lock.json</code> lists, and <code dir=\"ltr\">npm test</code> runs the validator.</li>\n</ul>\n<p>Any step that exits with an error fails the job. Commit the workflow, push the branch and open a pull request.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">Watching it run</h2>\n<p>Within seconds the pull request shows a check, “CI / check (pull_request)”, with a yellow dot while it runs. Then a green tick, or a red cross. Click <strong>Details</strong> to see the full log of every step; the failing one is expanded, showing the same validator output you’d get locally.</p>\n<p>To fix a red check, do what you’d do for review feedback: commit on the same branch and push. The workflow runs again on the new commit. Get used to reading these logs. CI isn’t judging you; it’s a colleague who reruns the checks every single time without getting bored.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 220\" role=\"img\" aria-labelledby=\"l-git-4-4-t1\"><defs><marker id=\"arrow-l-git-4-4-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-git-4-4-t1\">A left-to-right flow: feature branch, push, pull request, then two gates side by side, CI check passes and review approves, then merge into main. A blocked arrow shows direct pushes to main being rejected by the ruleset.</title>\n  <rect class=\"d-box\" x=\"10\" y=\"80\" width=\"110\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"65\" y=\"110\" text-anchor=\"middle\">branch</text>\n  <rect class=\"d-box\" x=\"150\" y=\"80\" width=\"120\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"210\" y=\"110\" text-anchor=\"middle\">pull request</text>\n  <rect class=\"d-box-success\" x=\"300\" y=\"40\" width=\"140\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"370\" y=\"67\" text-anchor=\"middle\">CI: check</text>\n  <rect class=\"d-box-success\" x=\"300\" y=\"126\" width=\"140\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"370\" y=\"153\" text-anchor=\"middle\">review</text>\n  <rect class=\"d-box-primary\" x=\"480\" y=\"80\" width=\"90\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"525\" y=\"110\" text-anchor=\"middle\">merge</text>\n  <rect class=\"d-box-accent\" x=\"600\" y=\"80\" width=\"90\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"645\" y=\"110\" text-anchor=\"middle\">main</text>\n  <path class=\"d-arrow\" d=\"M120 105 L146 105\" marker-end=\"url(#arrow-l-git-4-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M270 98 L296 70\" marker-end=\"url(#arrow-l-git-4-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M270 112 L296 140\" marker-end=\"url(#arrow-l-git-4-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M440 62 L476 96\" marker-end=\"url(#arrow-l-git-4-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M440 148 L476 114\" marker-end=\"url(#arrow-l-git-4-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M570 105 L596 105\" marker-end=\"url(#arrow-l-git-4-4-1)\"/>\n  <path class=\"d-line d-dashed\" d=\"M65 130 C65 205 600 205 640 136\"/>\n  <rect class=\"d-box-warn\" x=\"250\" y=\"186\" width=\"200\" height=\"30\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"350\" y=\"206\" text-anchor=\"middle\">direct push: rejected</text>\n</svg><figcaption>The protected path to main</figcaption></figure>\n<h2 id=\"sec-4\" tabindex=\"-1\">Protect main with a ruleset</h2>\n<p>A passing check is only useful if merging waits for it. That’s what a <strong>ruleset</strong> enforces. On GitHub, open the repository’s <strong>Settings</strong>, then <strong>Rules</strong>, then <strong>Rulesets</strong>, and choose <strong>New ruleset</strong>, <strong>New branch ruleset</strong>:</p>\n<ol>\n<li>Name it “Protect main” and set <strong>Enforcement status</strong> to Active.</li>\n<li>Under <strong>Target branches</strong>, add the default branch.</li>\n<li>Keep <strong>Restrict deletions</strong> and <strong>Block force pushes</strong> ticked.</li>\n<li>Tick <strong>Require a pull request before merging</strong>. On a solo repository set required approvals to 0 (you can’t approve your own pull request); on a team, 1 or more.</li>\n<li>Tick <strong>Require status checks to pass</strong>, add the check <code dir=\"ltr\">check</code>, and save.</li>\n</ol>\n<p>Now try to skip the process:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> switch</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> main</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> commit</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --allow-empty</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -m</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"Sneak a commit onto main\"</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> push</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> origin</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> main</span></span></code></pre></div>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"text\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">Text</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code>remote: error: GH013: Repository rule violations found for refs/heads/main.</code></pre></div>\n<p>The push is refused, and your local commit is untouched. Every change to <code dir=\"ltr\">main</code> now goes through a pull request, and the merge button stays disabled until the check is green. Run <code dir=\"ltr\">git reset --hard origin/main</code> to drop the empty commit.</p>\n<p>Rulesets are free on public repositories; on private ones they need a paid plan such as GitHub Pro or Team. You may also see the older <strong>branch protection rules</strong> in settings. They do a similar job; rulesets are newer, can be layered, and anyone with read access can see which rules apply.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Requiring a check that never reports</span></p>\n<p>Required checks are matched by name. If you require <code dir=\"ltr\">check</code> before the workflow has ever run, GitHub can’t offer it in the list; and if you later rename the job, pull requests wait forever on “Expected — Waiting for status to be reported”. Run the workflow once before requiring it, and change the job name and the ruleset together.</p>\n</aside>\n<h2 id=\"sec-5\" tabindex=\"-1\">What you can do now</h2>\n<p>Look at where the portfolio started: a folder with <code dir=\"ltr\">index.html</code>. It’s now a repository with readable history, branches that merge or rebase cleanly, a GitHub remote you reach with proper authentication, issues that explain why work exists, tagged releases, and a <code dir=\"ltr\">main</code> that only accepts reviewed, automatically checked pull requests. You can undo almost any mistake, and you know which undo fits.</p>\n<p>More than the commands, you have the model: commits are snapshots linked to parents, branches and tags are labels, HEAD is where you stand, and remotes are other copies of the same graph. When Git surprises you in future, run <code dir=\"ltr\">git log --oneline --graph --all</code>, draw what you see, and the next command will usually be obvious.</p>\n","toc":[{"id":"sec-1","text":"Give the portfolio something to check","level":2},{"id":"sec-2","text":"Your first workflow","level":2},{"id":"sec-3","text":"Watching it run","level":2},{"id":"sec-4","text":"Protect main with a ruleset","level":2},{"id":"sec-5","text":"What you can do now","level":2}],"takeaways":["A workflow is a YAML file in <code dir=\"ltr\">.github/workflows/</code>; <code dir=\"ltr\">on</code> picks the events, <code dir=\"ltr\">jobs</code> run on runners, and <code dir=\"ltr\">steps</code> either <code dir=\"ltr\">uses</code> an action or <code dir=\"ltr\">run</code>s a command.","Each job reports a status check on the pull request, shown as a green tick or a red cross with full logs.","A branch ruleset on <code dir=\"ltr\">main</code> can block force pushes and deletions, require a pull request, and require named status checks to pass.","A required status check is matched by job name, and GitHub only suggests checks that have already reported, so run the workflow before requiring it.","The finished loop is branch, push, pull request, automated check, review, merge, and nobody can skip a step."],"further":[{"title":"Building and testing Node.js","url":"https://docs.github.com/en/actions/tutorials/build-and-test-code/nodejs"},{"title":"Workflow syntax for GitHub Actions","url":"https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax"},{"title":"About rulesets","url":"https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets"},{"title":"Available rules for rulesets","url":"https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets"}],"quiz":[{"q":"<p>Your workflow starts with <code dir=\"ltr\">on: pull_request</code>. When does it run?</p>","options":[{"text":"Only when someone clicks Run workflow in the Actions tab.","why":"That’s the <code dir=\"ltr\">workflow_dispatch</code> event. <code dir=\"ltr\">pull_request</code> is triggered automatically by pull request activity."},{"text":"When a pull request is opened, and again each time new commits are pushed to its branch.","why":"Correct. By default <code dir=\"ltr\">pull_request</code> runs on opened, synchronize (new commits) and reopened, so every update is checked."},{"text":"Only after the pull request has been merged.","why":"The point of checking a pull request is to know before merging. A run after merge would be a <code dir=\"ltr\">push</code> to <code dir=\"ltr\">main</code>."},{"text":"Every time anyone pushes to any branch.","why":"That’s the <code dir=\"ltr\">push</code> event without a branch filter. <code dir=\"ltr\">pull_request</code> only fires for pull request activity."}],"answer":1},{"q":"<p>Your CI job fails at <code dir=\"ltr\">npm ci</code> with an error that <code dir=\"ltr\">package.json</code> can’t be found. The first step is <code dir=\"ltr\">run: npm ci</code>. What’s wrong?</p>","options":[{"text":"The runner doesn’t have npm installed.","why":"GitHub’s Ubuntu runners include Node.js and npm. The error is about a missing file, not a missing tool."},{"text":"<code dir=\"ltr\">npm ci</code> only works on Windows runners.","why":"<code dir=\"ltr\">npm ci</code> works on every operating system the runners offer."},{"text":"The workflow file is in the wrong folder.","why":"If the file were in the wrong folder the workflow wouldn’t run at all. It ran, and failed at a step."},{"text":"The job never checked out the repository; <code dir=\"ltr\">actions/checkout</code> must come before any step that needs your files.","why":"Correct. A runner starts as an empty machine. <code dir=\"ltr\">actions/checkout</code> puts your repository’s files in place."}],"answer":3},{"q":"<p>You’ve added a ruleset on <code dir=\"ltr\">main</code> requiring a pull request. What happens when you run <code dir=\"ltr\">git push origin main</code> with a local commit?</p>","options":[{"text":"The push is rejected with a repository rule violation; the commit has to arrive through a pull request.","why":"Correct. GitHub refuses the update to <code dir=\"ltr\">main</code>, and your local commit is untouched, so you can push it to a branch and open a pull request."},{"text":"The push succeeds and GitHub opens a pull request automatically.","why":"GitHub doesn’t convert pushes into pull requests. Rules either allow an update or reject it."},{"text":"The push succeeds because rules only apply to other people.","why":"Rules apply to everyone, including the owner, unless you add yourself to the bypass list."},{"text":"Your local commit is deleted to keep you in sync with <code dir=\"ltr\">main</code>.","why":"Remote rules can’t touch your local repository. Nothing local changes when a push is rejected."}],"answer":0},{"q":"<p>You renamed your workflow job from <code dir=\"ltr\">check</code> to <code dir=\"ltr\">html</code>. Now every pull request shows the required check <code dir=\"ltr\">check</code> as “Expected — Waiting for status to be reported”, and nothing can merge. Why?</p>","options":[{"text":"Rulesets can only require checks from jobs named <code dir=\"ltr\">check</code> or <code dir=\"ltr\">test</code>.","why":"Any job name works. The problem is that the ruleset and the workflow now disagree about the name."},{"text":"The workflow has a YAML syntax error.","why":"The <code dir=\"ltr\">html</code> job runs fine; it just reports under a name the ruleset isn’t waiting for."},{"text":"The ruleset still requires a check named <code dir=\"ltr\">check</code>, which no job reports any more; update the required check to <code dir=\"ltr\">html</code>.","why":"Correct. Required checks are matched by name. Rename one, and update the other at the same time."}],"answer":2}],"exercise":{"kind":"spot-bug","language":"yaml","code":"name: CI\n\non:\n  pull_requests:\n    branches: [main]\n\njobs:\n  check:\n    runs-on: ubuntu-latest\n    steps:\n      - run: npm ci\n      - uses: actions/checkout@v7\n      - uses: actions/setup-node\n        with:\n          node-version: 24\n          cache: npm\n      - run: npm test","bugLines":[4,11,13],"title":"Debug the CI workflow","prompt":"<p>A teammate wrote <code dir=\"ltr\">.github/workflows/ci.yml</code> to run the portfolio’s HTML check on every pull request into <code dir=\"ltr\">main</code>. It never appears on pull requests, and once that’s fixed it would still fail. Select the three lines that are wrong.</p>\n","hints":["Event names in <code dir=\"ltr\">on</code> are exact; compare with the event name you saw in the lesson.","A runner starts empty. What has to happen before <code dir=\"ltr\">npm</code> can find <code dir=\"ltr\">package.json</code>?","Every <code dir=\"ltr\">uses:</code> needs to say which version of the action to run."],"explanation":"<p>Line 4: the event is <code dir=\"ltr\">pull_request</code> (singular); <code dir=\"ltr\">pull_requests</code> isn’t an event GitHub recognises, so the workflow is invalid and never runs on pull requests. Line 11: <code dir=\"ltr\">npm ci</code> runs before <code dir=\"ltr\">actions/checkout</code>, on an empty runner with no <code dir=\"ltr\">package.json</code>; move it after checkout and setup-node. Line 13: <code dir=\"ltr\">uses</code> must include a ref, such as <code dir=\"ltr\">actions/setup-node@v7</code>; without <code dir=\"ltr\">@…</code> the workflow fails to load. Fixed, the steps read: checkout, setup-node with Node 24, <code dir=\"ltr\">npm ci</code>, <code dir=\"ltr\">npm test</code>.</p>\n","checkLabels":{}},"words":796,"readMinutes":4}