<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Codechips-cicd]]></title><description><![CDATA[Codechips-cicd]]></description><link>https://codechips-ci.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6ae7972de4285f73fdb3727e/4b2b2784-fc85-4591-9199-25eee81fe81d.png</url><title>Codechips-cicd</title><link>https://codechips-ci.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sun, 08 Nov 2026 14:57:28 GMT</lastBuildDate><atom:link href="https://codechips-ci.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[CI/CD with GitHub Actions: Building the Pib Patrol]]></title><description><![CDATA[Watch on Youtube (Hand-drawn Animations) (No AI): A junior dev, a senior dev, a pizza that is also a burger, and every GitHub Actions concept you need to…]]></description><link>https://codechips-ci.hashnode.dev/ci-cd-with-github-actions-building-the-pib-patrol</link><guid isPermaLink="true">https://codechips-ci.hashnode.dev/ci-cd-with-github-actions-building-the-pib-patrol</guid><category><![CDATA[ci-cd]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[github-actions]]></category><category><![CDATA[Devops]]></category><category><![CDATA[QA]]></category><category><![CDATA[sdet]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[coding]]></category><dc:creator><![CDATA[Codechips]]></dc:creator><pubDate>Sat, 07 Nov 2026 13:55:30 GMT</pubDate><content:encoded><![CDATA[<p>Watch on Youtube (Hand-drawn Animations) (No AI): <a href="https://youtu.be/0sqIkd4UBQg">https://youtu.be/0sqIkd4UBQg</a></p>
<p><a class="embed-card" href="https://youtu.be/0sqIkd4UBQg">https://youtu.be/0sqIkd4UBQg</a></p>

<p><em>A junior dev, a senior dev, a pizza that is also a burger, and every GitHub Actions concept you need to read (and write) a real workflow.</em></p>
<img src="https://cdn.hashnode.com/uploads/covers/6ae7972de4285f73fdb3727e/2776ba85-8229-4544-8892-f89fcdaaa13d.png" alt="" style="display:block;margin:0 auto" />

<h2>1. Why do we need CI/CD?</h2>
<p>A junior developer asks a senior to review his code. The senior asks if he ran the unit tests. “Should I?” Yes. Then static analysis. Then lint. Then coverage. Each time, the junior runs back, annoyed that nobody gave him the full list at once. Finally everything is “done,” the senior says LGTM, and the code is merged.</p>
<p>Days later, the senior coughs. “Did you test it properly?” while the junior responds angrily “What do you mean by properly, I even found bugs later and being the talented dev, fixed them as well.” and the senior follows up with “Did you test it <em>after</em> your fix?” The junior offers him an ice cream to cool down but the Production is already on fire.</p>
<p>Nothing here was a talent problem. It was a <strong>process</strong> problem:</p>
<ul>
<li>Humans forget steps, especially after “just a small fix.”</li>
<li>Manual checking doesn’t scale as the team and the code grow.</li>
<li>Bugs found late are far more expensive than bugs found minutes after the change.</li>
</ul>
<p>CI/CD exists so that the checklist runs <strong>automatically, identically, on every change</strong>, whether or not anyone remembers.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ae7972de4285f73fdb3727e/56863909-a94b-4fb8-80a7-4dfd46c0c6a1.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>2. What is CI/CD?</h2>
<p><strong>Continuous Integration (CI)</strong> means every code change is automatically built and tested in a clean environment, and changes are merged into the shared branch often so problems surface while they’re small. Typical CI checks:</p>
<ul>
<li><strong>Build verification:</strong> does it compile or package?</li>
<li><strong>Unit tests:</strong> does each small piece work alone?</li>
<li><strong>Integration tests:</strong> do the pieces work together?</li>
<li><strong>Lint and static analysis:</strong> is the style consistent, and does the code contain known risky patterns?</li>
<li><strong>Coverage:</strong> how much of the code did the tests actually exercise?</li>
</ul>
<p>The “CD” has two meanings, and interviewers like asking which one you mean:</p>
<table>
<thead>
<tr>
<th>Term</th>
<th>What it means</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Continuous Delivery</strong></td>
<td>Every change that passes CI is automatically prepared for release. A human still approves the final push to production.</td>
</tr>
<tr>
<td><strong>Continuous Deployment</strong></td>
<td>Every change that passes all checks goes to production automatically, with no human approval.</td>
</tr>
</tbody></table>
<p>CI doesn’t replace running quick checks locally. Do both: local checks for fast feedback, CI as the safety net that doesn’t depend on anyone’s memory.</p>
<hr />
<h2>3. What is GitHub Actions?</h2>
<p><strong>GitHub Actions</strong> is GitHub’s built-in automation platform. You describe automation in a YAML file inside your repository, and GitHub runs it in response to events such as a push or a pull request. Official starting point: <a href="https://docs.github.com/en/actions">GitHub Actions documentation</a>.</p>
<p>The vocabulary, mapped to our pib factory (a food company that makes pizza-burgers, and now builds a checking department so no bad pib reaches customers):</p>
<table>
<thead>
<tr>
<th>GitHub Actions term</th>
<th>In the pib factory</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Workflow</strong></td>
<td>A checking department, one automated process</td>
</tr>
<tr>
<td><strong>Event / trigger</strong></td>
<td>The department’s entrance door</td>
</tr>
<tr>
<td><strong>Job</strong></td>
<td>A fresh, isolated room inside the department</td>
</tr>
<tr>
<td><strong>Runner</strong></td>
<td>The type of room (Linux, Windows, Mac)</td>
</tr>
<tr>
<td><strong>Step</strong></td>
<td>One task on the room’s checklist</td>
</tr>
<tr>
<td><strong>Action</strong></td>
<td>A ready-made appliance from the marketplace</td>
</tr>
<tr>
<td><strong>Output</strong></td>
<td>A postcard dropped in a postbox</td>
</tr>
<tr>
<td><strong>Artifact</strong></td>
<td>A package sent by courier</td>
</tr>
</tbody></table>
<p>A minimal workflow has this shape. We’ll fill in each part in the next sections:</p>
<pre><code class="language-yaml">name: pib_patrol          # 4. the workflow
on: [push]                # 5. the trigger
jobs:                     # 6. the jobs
  slice:
    runs-on: ubuntu-latest
    steps:                # 7-8. the steps
      - run: echo "hello"
</code></pre>
<hr />
<h2>4. The workflow: name and location</h2>
<p>A workflow is one YAML file. GitHub only discovers workflow files in this folder:</p>
<pre><code class="language-plaintext">.github/workflows/pib_patrol.yaml
</code></pre>
<p>Put the file anywhere else and nothing happens.</p>
<pre><code class="language-yaml">name: pib_patrol
</code></pre>
<p>Nuances:</p>
<ul>
<li><code>name</code> is the label shown in the repository’s <strong>Actions</strong> tab. If you omit it, GitHub shows the file path.</li>
<li>File extension can be <code>.yml</code> or <code>.yaml</code>.</li>
<li>A repository can have <strong>many workflows</strong>, for example one for tests, one for security scans and one for builds. Each is its own file and runs independently.</li>
<li>YAML is whitespace-sensitive: use spaces, never tabs.</li>
</ul>
<p>Reference: <a href="https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax">Workflow syntax for GitHub Actions</a>.</p>
<hr />
<h2>5. Triggers: the entrance doors (<code>on</code>)</h2>
<p>The <code>on</code> key lists the <strong>events</strong> that start the workflow. You can list one or several:</p>
<pre><code class="language-yaml">on:
  pull_request:
  push:
  workflow_dispatch:
  schedule:
    - cron: "0 0 * * *"
</code></pre>
<h3>5.1 <code>pull_request</code></h3>
<p>Runs when a pull request is <strong>opened</strong>, <strong>synchronized</strong> (new commits pushed to its branch) or <strong>reopened</strong>. Those three are the defaults. Change them with <code>types</code>, and limit to certain target branches with <code>branches</code>:</p>
<pre><code class="language-yaml">on:
  pull_request:
    branches: [main]                 # PRs targeting main
    types: [opened, synchronize, reopened, ready_for_review]
</code></pre>
<h3>5.2 <code>push</code></h3>
<p>Runs when commits are pushed. With no filters it fires for every branch. Narrow it down:</p>
<pre><code class="language-yaml">on:
  push:
    branches: [main]
    paths: ["src/**"]                # only when files under src/ changed
</code></pre>
<p>A popular setup: <code>pull_request</code> for all branches, <code>push</code> for <code>main</code> only.</p>
<h3>5.3 <code>workflow_dispatch</code> (manual run)</h3>
<p>Adds a <strong>Run workflow</strong> button in the Actions tab, and can also be started from the GitHub CLI or the REST API. It can take typed <strong>inputs</strong>:</p>
<pre><code class="language-yaml">on:
  workflow_dispatch:
    inputs:
      environment:
        description: "Where to run"
        type: choice
        options: [staging, production]
        default: staging
</code></pre>
<p>Read it later with <code>${{ inputs.environment }}</code>. The workflow file must exist on the <strong>default branch</strong> for the button to appear.</p>
<h3>5.4 <code>schedule</code> (cron)</h3>
<p>Runs the workflow at set times using <strong>POSIX cron syntax</strong>, five space-separated fields:</p>
<pre><code class="language-plaintext">┌───────── minute        (0 - 59)
│ ┌─────── hour          (0 - 23)
│ │ ┌───── day of month  (1 - 31)
│ │ │ ┌─── month         (1 - 12 or JAN-DEC)
│ │ │ │ ┌─ day of week   (0 - 6 or SUN-SAT)
│ │ │ │ │
* * * * *
</code></pre>
<p>Operators you can use in any field:</p>
<table>
<thead>
<tr>
<th>Operator</th>
<th>Meaning</th>
<th>Example</th>
</tr>
</thead>
<tbody><tr>
<td><code>*</code></td>
<td>every value</td>
<td><code>*</code> in hour = every hour</td>
</tr>
<tr>
<td><code>,</code></td>
<td>list</td>
<td><code>1,15</code> = the 1st and 15th</td>
</tr>
<tr>
<td><code>-</code></td>
<td>range</td>
<td><code>1-5</code> = Monday to Friday</td>
</tr>
<tr>
<td><code>/</code></td>
<td>step</td>
<td><code>*/15</code> = every 15 units</td>
</tr>
</tbody></table>
<pre><code class="language-yaml">on:
  schedule:
    - cron: "0 9 * * 1-5"
      timezone: "America/New_York"   # optional, see below
</code></pre>
<p>Things that surprise people:</p>
<ul>
<li><strong>Time zone:</strong> by default, schedules run in <strong>UTC</strong>. GitHub has added an optional <code>timezone</code> field (an IANA name such as <code>Asia/Kolkata</code>); without it, convert your local time to UTC yourself.</li>
<li><strong>Minimum interval is 5 minutes.</strong> <code>* * * * *</code> will not run every minute.</li>
<li><strong>Default branch only.</strong> Schedules run on the latest commit of the default branch. Changing the cron on a feature branch does nothing until it’s merged.</li>
<li><strong>Delays are normal.</strong> Runs can start late, especially at busy times such as the top of the hour. Offsetting to something like <code>17 3 * * *</code> helps.</li>
<li><strong>Auto-disable:</strong> in public repositories, scheduled workflows are disabled after 60 days without repository activity, and they’re disabled by default in forks.</li>
<li><strong>Quote the cron string.</strong> A value starting with <code>*</code> is special in YAML.</li>
<li>You can list <strong>several</strong> <code>cron</code> entries. Use <code>github.event.schedule</code> to see which one fired.</li>
</ul>
<p>These are just the common ones. GitHub supports many more events, such as <code>release</code>, <code>issues</code>, <code>workflow_run</code> and <code>repository_dispatch</code>. See <a href="https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows">Events that trigger workflows</a> for the full list.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ae7972de4285f73fdb3727e/23b9978b-9d30-4ec4-9d5a-09732e66e2bc.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>6. Jobs and runners (<code>jobs</code>, <code>runs-on</code>)</h2>
<p>When a trigger fires, GitHub starts the workflow’s <strong>jobs</strong>. Each job is a group of steps that run on one machine.</p>
<pre><code class="language-yaml">jobs:
  slice:                    # job ID
    name: Slice the pib     # optional display name
    runs-on: ubuntu-latest
    timeout-minutes: 15
</code></pre>
<p>Nuances:</p>
<ul>
<li><strong>Job ID rules:</strong> it must start with a letter or <code>_</code> and contain only letters, numbers, <code>-</code> and <code>_</code>. No spaces. Use <code>name:</code> for a friendly label.</li>
<li><strong>Jobs run in parallel by default</strong> and know nothing about each other (see section 9).</li>
<li><strong>Fresh machine every time.</strong> On GitHub-hosted runners each job gets a brand-new virtual machine that is discarded when the job ends. That’s what makes CI a <em>clean environment</em>, and also why nothing carries over between jobs on its own.</li>
<li><strong>Steps inside one job share the machine</strong>, so a file created in one step is visible in the next.</li>
<li><strong>Timeouts:</strong> a GitHub-hosted job can run up to <strong>6 hours</strong>, then it’s terminated; set <code>timeout-minutes</code> to fail faster. A whole workflow run is cancelled at <strong>35 days</strong>. See <a href="https://docs.github.com/en/actions/reference/actions-limits">Actions limits</a>.</li>
</ul>
<h3>Choosing the room: <code>runs-on</code></h3>
<table>
<thead>
<tr>
<th>Label</th>
<th>Machine</th>
</tr>
</thead>
<tbody><tr>
<td><code>ubuntu-latest</code></td>
<td>Linux, the usual default</td>
</tr>
<tr>
<td><code>windows-latest</code></td>
<td>Windows</td>
</tr>
<tr>
<td><code>macos-latest</code></td>
<td>macOS (usually the most expensive)</td>
</tr>
<tr>
<td><code>ubuntu-24.04</code></td>
<td>A specific, pinned Linux version</td>
</tr>
<tr>
<td><code>self-hosted</code></td>
<td>Your own machine</td>
</tr>
</tbody></table>
<p><code>-latest</code> is a moving label: when GitHub promotes a newer image, the same word points to a new OS version. Pin the version if you need stability. Self-hosted runners are machines you maintain yourself, for special hardware or private networks; they can keep files between jobs unless you clean them, and they have their own <a href="https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/usage-limits-for-self-hosted-runners">usage limits</a>.</p>
<p><em>Teaser for another day:</em> a <strong>matrix</strong> (<code>strategy.matrix</code>) can create many jobs, such as 3 operating systems × 3 language versions, from one definition, up to 256 jobs per run.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ae7972de4285f73fdb3727e/03286b3d-645e-4596-a46d-82cb0870ba69.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>7. Steps and <code>run</code>: your own commands</h2>
<p>A job’s <code>steps</code> run <strong>one after another, top to bottom</strong>, on the same runner. Each step is either a command (<code>run</code>) or a reusable action (<code>uses</code>, next section).</p>
<pre><code class="language-yaml">steps:
      - name: Slice the pib
        id: slice
        run: echo "Slicing the pib..."
</code></pre>
<ul>
<li><code>name</code> is the label in the logs.</li>
<li><code>id</code> lets other parts of the workflow refer to this step (we need it for outputs).</li>
</ul>
<h3>Running several commands: the <code>|</code> pipe</h3>
<p>The <code>|</code> after <code>run:</code> is <strong>YAML block syntax</strong>, not a shell pipe. It keeps line breaks, so each line becomes a separate command in the same shell:</p>
<pre><code class="language-yaml"> - name: Prepare and slice
        run: |
          echo "Heating the oven"
          mkdir -p out
          echo "4 slices" &gt; out/report.txt
</code></pre>
<p>Nuances:</p>
<ul>
<li>On Linux and macOS the default shell is <strong>bash with</strong> <code>-e</code>, so if any line fails, the step stops and fails right there.</li>
<li>Lines share one shell, so a variable set on line 1 is visible on line 3.</li>
<li>Use <code>env:</code> to pass environment variables to a step.</li>
</ul>
<p>In a real project, this is where <code>pytest</code>, <code>npm test</code>, <code>mvn verify</code> or your linter goes.</p>
<hr />
<h2>8. Actions and the Marketplace: <code>uses</code></h2>
<p>A fresh runner is empty. To get your repository’s code onto it, you could script authentication, cloning and private-repo handling yourself, or you can reuse what someone has already built. That is <code>uses</code>:</p>
<pre><code class="language-yaml">      - name: Get the recipe
        uses: actions/checkout@v6
</code></pre>
<p>An <strong>action</strong> is a reusable unit of automation, shared through the <a href="https://github.com/marketplace?type=actions">GitHub Marketplace</a> or any repository. Inputs go under <code>with:</code></p>
<pre><code class="language-yaml">      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
</code></pre>
<p>The format is <code>owner/repo@version</code>. Nuances:</p>
<ul>
<li><strong>The version after</strong> <code>@</code> can be a tag (<code>@v6</code>), a branch, or a full commit SHA. Tags can move, so security-conscious teams pin to a commit SHA.</li>
<li><strong>Treat third-party actions as dependencies.</strong> They run inside your pipeline: prefer trusted publishers, read the source, and keep versions updated.</li>
<li><code>actions/checkout</code> fetches only the latest commit by default (shallow clone). Use <code>fetch-depth: 0</code> when you need full history.</li>
<li><strong>Rule of thumb:</strong> <code>uses</code> calls someone else’s action, <code>run</code> runs your own command.</li>
</ul>
<p>Popular first stops: <a href="https://github.com/actions/checkout">actions/checkout</a>, <a href="https://github.com/actions/setup-python">actions/setup-python</a>, <a href="https://github.com/actions/upload-artifact">actions/upload-artifact</a>, <a href="https://github.com/actions/download-artifact">actions/download-artifact</a>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ae7972de4285f73fdb3727e/8ad28124-56b5-46a9-a1d9-1c64a4a76fe1.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>9. Running together, then waiting: <code>needs</code></h2>
<p>Add a second job next to <code>slice</code>:</p>
<pre><code class="language-yaml">  taste:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Tasting the pib"
</code></pre>
<p>Run it, and both jobs <strong>start at the same moment</strong>. <code>taste</code> has nothing to taste but still turns green, because jobs are independent by default. Parallel-by-default keeps pipelines fast; you just have to declare real dependencies:</p>
<pre><code class="language-yaml">  taste:
    needs: slice
    runs-on: ubuntu-latest
</code></pre>
<p>Nuances:</p>
<ul>
<li><code>needs</code> makes the job wait until the needed job <strong>finishes successfully</strong>.</li>
<li><strong>If the needed job fails, the dependent job is skipped</strong>, not failed. Override with <code>if: ${{ always() }}</code> for cleanup or notification jobs.</li>
<li>Depend on several jobs with a list: <code>needs: [slice, build]</code>. It waits for all of them.</li>
<li>Chains work (C needs B needs A), but circular dependencies are rejected.</li>
<li><code>needs</code> is also what lets a job read another job’s <strong>outputs</strong>.</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/6ae7972de4285f73fdb3727e/63e9fc2c-7b62-4b7b-b851-eb931e18cd3d.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>10. Passing small data: outputs</h2>
<p>Jobs run on separate machines, so one job can’t read another’s variables. To send small values forward, use <strong>outputs</strong>. It takes three hops:</p>
<pre><code class="language-yaml">jobs:
  slice:
    runs-on: ubuntu-latest
    outputs:
      count: ${{ steps.slice.outputs.count }}   # 2. expose it
    steps:
      - name: Slice the pib
        id: slice
        run: echo "count=4" &gt;&gt; "$GITHUB_OUTPUT"   # 1. write it

  taste:
    needs: slice
    runs-on: ubuntu-latest
    steps:
      - run: echo "Tasting ${{ needs.slice.outputs.count }} slices"   # 3. read it
</code></pre>
<ol>
<li><strong>A step writes</strong> <code>name=value</code> to the file named by <code>$GITHUB_OUTPUT</code>. (It’s <code>GITHUB_OUTPUT</code>, singular; the old <code>::set-output</code> command is deprecated.)</li>
<li><strong>The job exposes it</strong> in its <code>outputs:</code> block, using the step’s <code>id</code>.</li>
<li><strong>Another job reads it</strong> as <code>needs.&lt;job_id&gt;.outputs.&lt;name&gt;</code>, which requires <code>needs</code>.</li>
</ol>
<p>Nuances:</p>
<ul>
<li>Outputs are <strong>strings</strong> and meant to be small: a version, a count, a flag. They have size limits (see the docs).</li>
<li>A step output doesn’t leave its job unless you map it in <code>outputs:</code>.</li>
</ul>
<p>Official guide: <a href="https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/pass-job-outputs">Pass job outputs</a>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ae7972de4285f73fdb3727e/29953998-c753-42e8-a089-06f258425ab7.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>11. Passing files: artifacts</h2>
<p>A report, a build, or a pile of test results won’t fit in a postcard. <strong>Artifacts</strong> store files from a job so other jobs, or you, can download them later.</p>
<pre><code class="language-yaml">  slice:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Pib cut into 4 slices" &gt; report.txt
      - uses: actions/upload-artifact@v4
        with:
          name: pib-report
          path: report.txt
          retention-days: 30          # optional

  check_cuts:
    needs: slice                      # wait, or there is nothing to download
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: pib-report
      - run: cat report.txt
</code></pre>
<h3>How long are artifacts kept?</h3>
<ul>
<li><strong>Default: 90 days.</strong> Artifacts and logs are deleted automatically after that.</li>
<li><strong>Public repositories:</strong> you can set retention anywhere from <strong>1 to 90 days</strong>.</li>
<li><strong>Private and internal repositories:</strong> anywhere from <strong>1 to 400 days</strong>.</li>
<li>Set it <strong>per artifact</strong> with <code>retention-days</code> on the upload step, or as a default for a repository, organization or enterprise in settings. A repository can’t exceed the cap set by the organization or enterprise that manages it.</li>
<li>A new retention setting applies only to <strong>new</strong> artifacts, not existing ones.</li>
<li>Separate from this, <strong>workflow run history</strong> is kept 400 days and that can’t be changed.</li>
</ul>
<p>See <a href="https://docs.github.com/en/organizations/managing-organization-settings/configuring-the-retention-period-for-github-actions-artifacts-and-logs-in-your-organization">Configuring retention for artifacts and logs</a> and <a href="https://docs.github.com/actions/learn-github-actions/usage-limits-billing-and-administration">usage limits and storage</a>. Artifact storage counts toward your plan’s quota.</p>
<h3>Other artifact details</h3>
<ul>
<li><strong>Upload options:</strong> <code>name</code>, <code>path</code> (a file, folder or glob), <code>retention-days</code>, <code>if-no-files-found</code> (<code>warn</code>, <code>error</code> or <code>ignore</code>) and <code>overwrite</code>.</li>
<li><strong>Names must be unique within a run</strong> with <code>upload-artifact@v4</code>. Jobs on several operating systems should put the OS in the name (for example <code>report-${{ runner.os }}</code>).</li>
<li><strong>Download by name</strong>, or omit <code>name</code> to download all artifacts of the run; each lands in its own folder.</li>
<li><strong>Downloading from another workflow run</strong> needs the <code>run-id</code> and a <code>github-token</code>.</li>
<li>You can also download artifacts by hand from the workflow run page.</li>
<li><strong>Artifacts vs. cache:</strong> artifacts keep <em>results</em> you want to pass on or inspect. <code>actions/cache</code> stores <em>dependencies</em> so the next run is faster.</li>
</ul>
<blockquote>
<p><strong>Outputs vs. artifacts in one line:</strong> outputs are small strings passed between jobs; artifacts are files that outlive the job.</p>
</blockquote>
<p>Repos: <a href="https://github.com/actions/upload-artifact">upload-artifact</a> and <a href="https://github.com/actions/download-artifact">download-artifact</a>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ae7972de4285f73fdb3727e/fbb59e15-8a16-42b8-b20b-bf7b0bfb83fe.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>12. The whole pib_patrol</h2>
<pre><code class="language-yaml">name: pib_patrol

on:
  pull_request:
  push:
  workflow_dispatch:
  schedule:
    - cron: "0 0 * * *"

jobs:
  slice:
    runs-on: ubuntu-latest
    outputs:
      count: ${{ steps.slice.outputs.count }}
    steps:
      - name: Get the recipe
        uses: actions/checkout@v6

      - name: Slice the pib
        id: slice
        run: |
          echo "Slicing the pib..."
          echo "count=4" &gt;&gt; "$GITHUB_OUTPUT"
          echo "Pib cut into 4 slices" &gt; report.txt

      - name: Upload report
        uses: actions/upload-artifact@v4
        with:
          name: pib-report
          path: report.txt

  taste:
    needs: slice
    runs-on: ubuntu-latest
    steps:
      - name: Taste the slices
        run: echo "Tasting ${{ needs.slice.outputs.count }} slices"

  check_cuts:
    needs: slice
    runs-on: ubuntu-latest
    steps:
      - name: Download report
        uses: actions/download-artifact@v4
        with:
          name: pib-report

      - name: Check report
        run: cat report.txt
</code></pre>
<p>Read the flow: a trigger opens the door, <code>slice</code> runs, and when it finishes <code>taste</code> and <code>check_cuts</code> start <strong>in parallel</strong>, one reading a postcard and the other collecting a courier package. In a real project, <code>slice</code> becomes your build, <code>taste</code> your tests, and <code>check_cuts</code> your lint, coverage or report checks.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ae7972de4285f73fdb3727e/bd0d27e4-2e41-4101-9165-e4df216987b1.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>13. Quick reference and what’s next</h2>
<table>
<thead>
<tr>
<th>Item</th>
<th>Value</th>
</tr>
</thead>
<tbody><tr>
<td>Workflow folder</td>
<td><code>.github/workflows/</code></td>
</tr>
<tr>
<td><code>pull_request</code> default types</td>
<td>opened, synchronize, reopened</td>
</tr>
<tr>
<td>Cron fields</td>
<td>minute, hour, day of month, month, day of week</td>
</tr>
<tr>
<td>Cron time zone</td>
<td>UTC by default (optional <code>timezone</code> field)</td>
</tr>
<tr>
<td>Shortest schedule interval</td>
<td>5 minutes</td>
</tr>
<tr>
<td>Scheduled runs</td>
<td>Default branch only</td>
</tr>
<tr>
<td>Job time limit (GitHub-hosted)</td>
<td>6 hours</td>
</tr>
<tr>
<td>Workflow run limit</td>
<td>35 days</td>
</tr>
<tr>
<td>Matrix limit</td>
<td>256 jobs per run</td>
</tr>
<tr>
<td>Artifact retention default</td>
<td>90 days</td>
</tr>
<tr>
<td>Artifact retention range</td>
<td>1-90 days public, 1-400 days private/internal</td>
</tr>
<tr>
<td>Workflow run history</td>
<td>400 days, fixed</td>
</tr>
</tbody></table>
<p><strong>Where to go next:</strong></p>
<ul>
<li><strong>Make checks mandatory.</strong> With branch protection and required status checks, a pull request can’t merge until the pipeline passes. Without that, CI is only advice.</li>
<li><strong>Conditions</strong> (<code>if:</code>) to run steps or jobs only in certain situations, <strong>secrets</strong> for credentials, <strong>caching</strong> for speed, and <strong>matrix</strong> builds.</li>
<li><strong>Reusable workflows and custom actions</strong> to share automation across repositories.</li>
<li><strong>Continuous delivery</strong>, so changes that pass every check reach users automatically.</li>
</ul>
<p>All limits and defaults can change, so double-check them in the <a href="https://docs.github.com/en/actions/reference/actions-limits">official limits page</a> before you rely on them.</p>
<hr />
<h2>Remember</h2>
<p>Continuously integrate, or someone will end up under this:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ae7972de4285f73fdb3727e/f289f025-8e65-493b-8e87-df0f0f7ce40f.png" alt="" style="display:block;margin:0 auto" />

<blockquote>
<p><strong>REST IN PRODUCTION</strong></p>
</blockquote>
]]></content:encoded></item></channel></rss>