<?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: Barbaros Karagülmez</title>
    <description>The latest articles on DEV Community by Barbaros Karagülmez (@barbarosdev).</description>
    <link>https://dev.to/barbarosdev</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%2F4124145%2F769c4f4b-269a-4ca1-87a7-5d2bdf8dbe14.jpg</url>
      <title>DEV Community: Barbaros Karagülmez</title>
      <link>https://dev.to/barbarosdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/barbarosdev"/>
    <language>en</language>
    <item>
      <title>Stop pushing 20 "test ci" commits: I built a tool to test GitHub Actions in the browser</title>
      <dc:creator>Barbaros Karagülmez</dc:creator>
      <pubDate>Mon, 14 Sep 2026 08:27:09 +0000</pubDate>
      <link>https://dev.to/barbarosdev/stop-pushing-20-test-ci-commits-i-built-a-tool-to-test-github-actions-in-the-browser-5apf</link>
      <guid>https://dev.to/barbarosdev/stop-pushing-20-test-ci-commits-i-built-a-tool-to-test-github-actions-in-the-browser-5apf</guid>
      <description>&lt;p&gt;If you've ever worked with GitHub Actions, you probably know the most frustrating part of the CI/CD experience: &lt;strong&gt;Complex &lt;code&gt;if:&lt;/code&gt; conditions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You write your workflow, push your code, and wait for the runner. Then, you see the dreaded gray icon: &lt;strong&gt;Skipped&lt;/strong&gt;. GitHub gives absolutely zero explanation about &lt;em&gt;why&lt;/em&gt; your condition evaluated to false.&lt;/p&gt;

&lt;p&gt;To debug this, I used to do what every sane developer does: pollute my commit history with garbage.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;"test ci"&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;"trigger build pls"&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;"fix condition maybe?"&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;"pls work"&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I got completely sick of this trial-and-error loop. I wanted a way to test my context payload and expressions locally before pushing anything. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: "Why Didn't My Job Run?"
&lt;/h3&gt;

&lt;p&gt;Instead of building a backend service to parse these expressions (which would mean paying for servers and sending private CI payloads over the network), I took a different approach.&lt;/p&gt;

&lt;p&gt;GitHub publishes the actual parser and expression engine it uses as open-source npm packages: &lt;code&gt;@actions/workflow-parser&lt;/code&gt; and &lt;code&gt;@actions/expressions&lt;/code&gt;. I bundled those two packages so they run in your browser. No reimplementation, no server.&lt;/p&gt;

&lt;p&gt;Then, I built a clean UI around it. The result is a tool that runs 100% locally in your browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here is how it works:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You paste your workflow file.&lt;/li&gt;
&lt;li&gt;You pick an event: a push to &lt;code&gt;main&lt;/code&gt;, a pull request from a fork, a label, a manual dispatch.&lt;/li&gt;
&lt;li&gt;Every job turns green or grey with the reason next to it, down to the value of each sub-expression — evaluated by the same code GitHub runs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's checked against real runs: 65 recorded workflow runs, 698 job decisions replayed, all matching.&lt;/p&gt;

&lt;p&gt;Since there are no server calls, your private workflow data never leaves your machine. Privacy by design, and zero server costs for me! 😅&lt;/p&gt;

&lt;h3&gt;
  
  
  Try it out!
&lt;/h3&gt;

&lt;p&gt;I decided to open-source the entire thing. If you are tired of making dummy commits just to test your pipelines, give it a spin.&lt;/p&gt;

&lt;p&gt;🌐 &lt;strong&gt;Live Simulator:&lt;/strong&gt; &lt;a href="https://barbarkaragul-oss.github.io/why-didnt-my-job-run/" rel="noopener noreferrer"&gt;https://barbarkaragul-oss.github.io/why-didnt-my-job-run/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💻 &lt;strong&gt;GitHub Repo:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/barbarkaragul-oss" rel="noopener noreferrer"&gt;
        barbarkaragul-oss
      &lt;/a&gt; / &lt;a href="https://github.com/barbarkaragul-oss/why-didnt-my-job-run" rel="noopener noreferrer"&gt;
        why-didnt-my-job-run
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Paste a GitHub Actions workflow, pick the event, see which jobs run and why. GitHub's own workflow parser and expression engine, running in your browser.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Why didn't my job run?&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;b&gt;Paste a GitHub Actions workflow, pick the event, see which jobs run and why.&lt;/b&gt;&lt;br&gt;GitHub's own workflow parser and expression engine, running in your browser. Nothing is uploaded.&lt;/p&gt;
&lt;p&gt;
  &lt;a href="https://barbarkaragul-oss.github.io/why-didnt-my-job-run/" rel="nofollow noopener noreferrer"&gt;Open the simulator&lt;/a&gt; ·
  &lt;a href="https://github.com/barbarkaragul-oss/why-didnt-my-job-run#how-it-works" rel="noopener noreferrer"&gt;How it works&lt;/a&gt; ·
  &lt;a href="https://github.com/barbarkaragul-oss/why-didnt-my-job-run#verified-against-real-runs" rel="noopener noreferrer"&gt;Verified against real runs&lt;/a&gt; ·
  &lt;a href="https://github.com/barbarkaragul-oss/why-didnt-my-job-run#limits" rel="noopener noreferrer"&gt;Limits&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://github.com/barbarkaragul-oss/why-didnt-my-job-run/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img alt="CI" src="https://github.com/barbarkaragul-oss/why-didnt-my-job-run/actions/workflows/ci.yml/badge.svg"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667"&gt;&lt;img alt="License: MIT" src="https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://barbarkaragul-oss.github.io/why-didnt-my-job-run/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fbarbarkaragul-oss%2Fwhy-didnt-my-job-run%2FHEAD%2Fdocs%2Fdemo.gif" alt="Switching the sample workflow and the event: a push to main runs build, deploy and notify; the fork check runs on a push because null == false is true; a pull_request assigned event does not trigger the workflow; a docs-only push is filtered out by paths-ignore" width="900"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Every GitHub Actions user has stared at a grey "skipped" job and asked why. The usual answer is a dozen "test ci" commits. This page answers in a second: paste the workflow, choose &lt;code&gt;push&lt;/code&gt; to &lt;code&gt;main&lt;/code&gt; or a &lt;code&gt;pull_request&lt;/code&gt; from a fork with a &lt;code&gt;release&lt;/code&gt; label or a &lt;code&gt;workflow_dispatch&lt;/code&gt; with &lt;code&gt;deploy: true&lt;/code&gt;, and every job turns green or grey with the reason next to it, down to the value of each sub-expression.&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;if: github.event.pull_request.head.repo.fork == false
→ SKIPPED on pull_request from a fork
  github.event.pull_request.head.repo.fork is true → github.event.pull_request.head.repo.fork == false is false
→ RUNS on push (probably not what you meant):
  github.event.pull_request.head.repo.fork&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/barbarkaragul-oss/why-didnt-my-job-run" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Check out my portfolio for other dev tools I've built: &lt;a href="https://barbaros.dev" rel="noopener noreferrer"&gt;https://barbaros.dev&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I would love to get your feedback on the UI, or on any workflow where the verdict doesn't match what GitHub actually did. If you manage to break the parser, please let me know! Let's stop the "test ci" madness together.&lt;/p&gt;

</description>
      <category>github</category>
      <category>devops</category>
      <category>showdev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
