<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fawwaz</title>
    <description>The latest articles on DEV Community by Fawwaz (@fawwaz_hayaza).</description>
    <link>https://dev.to/fawwaz_hayaza</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4038394%2F208c65c7-875d-4a22-adee-c184eed61072.jpg</url>
      <title>DEV Community: Fawwaz</title>
      <link>https://dev.to/fawwaz_hayaza</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fawwaz_hayaza"/>
    <language>en</language>
    <item>
      <title>Why Are We Still Copy-Pasting YAML in GitHub Actions?</title>
      <dc:creator>Fawwaz</dc:creator>
      <pubDate>Tue, 21 Jul 2026 13:11:07 +0000</pubDate>
      <link>https://dev.to/fawwaz_hayaza/why-are-we-still-copy-pasting-yaml-in-github-actions-77m</link>
      <guid>https://dev.to/fawwaz_hayaza/why-are-we-still-copy-pasting-yaml-in-github-actions-77m</guid>
      <description>&lt;p&gt;If you manage a multi-repository architecture, you already know the pain I'm about to describe. &lt;/p&gt;

&lt;p&gt;You need to roll out a standardized CI/CD update. Maybe you’re bumping a Node version, swapping out a deprecated linter, or rotating a secret. In a perfect world, you update one template. In reality, you spend your afternoon copy-pasting the exact same 15 lines of YAML across 40 different &lt;code&gt;.github/workflows/&lt;/code&gt; files. &lt;/p&gt;

&lt;p&gt;It feels fundamentally wrong. As engineers, we are taught to keep our code DRY (Don't Repeat Yourself). Yet, when it comes to the infrastructure defining our deployments, we tolerate massive duplication.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Current State of Affairs
&lt;/h3&gt;

&lt;p&gt;Let's look at a realistic, everyday scenario. You have a matrix of jobs—say, building a project across Linux, macOS, and Windows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-linux&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cache dependencies&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;~/.npm&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm-${{ hashFiles('package-lock.json') }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;

  &lt;span class="na"&gt;build-mac&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;macos-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cache dependencies&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;~/.npm&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm-${{ hashFiles('package-lock.json') }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration drift happens slowly. One developer adds a security scanning step to the Linux job but forgets to add it to the macOS job. Suddenly, your pipelines are inconsistent, and debugging takes hours.&lt;/p&gt;

&lt;h3&gt;
  
  
  Existing Solutions (And Where They Fall Short)
&lt;/h3&gt;

&lt;p&gt;To be fair, GitHub hasn't completely ignored this problem. We currently have two main workarounds:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Reusable Workflows&lt;/strong&gt;&lt;br&gt;
Reusable workflows are excellent for massive, standardized pipelines (e.g., "Deploy to Production"). You define a workflow in a central repository and call it from your other repos.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The friction:&lt;/strong&gt; They can be incredibly rigid. If Repository A needs to tweak just &lt;em&gt;one&lt;/em&gt; step in the reusable workflow, you often end up creating a messy web of boolean inputs (&lt;code&gt;if: inputs.run_custom_step == true&lt;/code&gt;). Passing secrets into reusable workflows also introduces significant boilerplate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Composite Actions&lt;/strong&gt;&lt;br&gt;
Composite actions allow you to group a sequence of steps into a single action.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The friction:&lt;/strong&gt; They are great for small utilities, but they abstract away visibility. When a composite action fails, the logs can be harder to parse, and you still have to maintain them in a separate repository or dedicated path. &lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Why This Problem Still Exists
&lt;/h3&gt;

&lt;p&gt;If you write standard YAML outside of GitHub Actions, this problem is already solved. Standard YAML supports &lt;strong&gt;Anchors and Aliases&lt;/strong&gt;. You define a block once with &lt;code&gt;&amp;amp;anchor_name&lt;/code&gt; and reuse it anywhere with &lt;code&gt;*anchor_name&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;.setup_template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;setup&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-linux&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*setup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, the GitHub Actions parser aggressively strips out YAML anchors before execution. The community has been begging for this feature for years (Issue #1182 is practically legendary at this point), but it remains unsupported. &lt;/p&gt;

&lt;h3&gt;
  
  
  A Personal Observation
&lt;/h3&gt;

&lt;p&gt;After reading countless threads on Reddit and GitHub, it seems like everyone hits this exact wall. Some teams settle for the copy-paste nightmare. Others invest weeks building internal python scripts or Makefiles to template their YAML locally before pushing to GitHub. &lt;/p&gt;

&lt;p&gt;I'm an engineer, and I hate building internal tools for problems that shouldn't exist. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Validation Experiment
&lt;/h3&gt;

&lt;p&gt;I want to be perfectly clear: &lt;strong&gt;I am not launching a product.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;I've been mapping out the architecture for a lightweight, drop-in preprocessor. It would allow you to write advanced, DRY YAML (with anchors and includes) and securely compile it down to native GitHub YAML right before the run. No infrastructure changes, no vendor lock-in.&lt;/p&gt;

&lt;p&gt;But before I spend my nights and weekends writing the backend logic for this, I am trying to validate if this is a problem actually worth solving, or if it's just an inconvenience we've all learned to live with. &lt;/p&gt;

&lt;h3&gt;
  
  
  How does your team handle this?
&lt;/h3&gt;

&lt;p&gt;I want to hear from you. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  How are you currently handling YAML duplication? &lt;/li&gt;
&lt;li&gt;  Have you fully migrated to Reusable Workflows, or did you build a custom templating script? &lt;/li&gt;
&lt;li&gt;  Do you see a fatal flaw in the idea of a preprocessor?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let me know in the comments. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;(If you strongly believe this preprocessor needs to exist and want to follow the validation experiment, I set up a barebones &lt;a href="http://atlas-atlas101.vercel.app/?utm_source=devto&amp;amp;utm_medium=devto&amp;amp;utm_campaign=wave1" rel="noopener noreferrer"&gt;Waitlist / Landing Page here&lt;/a&gt;. But honestly, I'm more interested in hearing your workarounds.)&lt;/em&gt;&lt;/p&gt;




</description>
      <category>devops</category>
      <category>github</category>
      <category>cicd</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
