<?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: Necoz</title>
    <description>The latest articles on DEV Community by Necoz (necoz).</description>
    <link>https://dev.to/necoz</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%2Forganization%2Fprofile_image%2F15923%2F19042de3-8257-4cc5-b2c0-adebba8cbeaa.png</url>
      <title>DEV Community: Necoz</title>
      <link>https://dev.to/necoz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/necoz"/>
    <language>en</language>
    <item>
      <title>Necoz Has a New Home on DEV! 🐈🎉</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Sun, 08 Nov 2026 11:50:29 +0000</pubDate>
      <link>https://dev.to/necoz/necoz-has-a-new-home-on-dev-3454</link>
      <guid>https://dev.to/necoz/necoz-has-a-new-home-on-dev-3454</guid>
      <description>&lt;p&gt;Hoi hoi! 👋&lt;/p&gt;

&lt;p&gt;A little update from Necoz! 😸&lt;/p&gt;

&lt;p&gt;We've created our own organization here on DEV!&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__15923"&gt;
  &lt;a href="/necoz" class="ltag__user__link profile-image-link"&gt;
    &lt;div class="ltag__user__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F15923%2F19042de3-8257-4cc5-b2c0-adebba8cbeaa.png" alt="necoz image"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
      &lt;a href="/necoz" class="ltag__user__link"&gt;Necoz&lt;/a&gt;
      Follow
    &lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a href="/necoz" class="ltag__user__link"&gt;
        Necoz is a friendly open-source community building small, simple, and useful tools for developers. 🐈 

Everyone is welcome, from first-time contributors to experienced engineers. 

Let's build something awesome together! 😸
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;YAY!!! 🎉&lt;/p&gt;

&lt;p&gt;Necoz started with a few small open-source projects, and now we're slowly building a community around them.&lt;/p&gt;

&lt;p&gt;This will be another place to share what we're building, what we're learning, and the little discoveries we make along the way.&lt;/p&gt;

&lt;p&gt;And of course, we'd love to see more contributors sharing their own experiences too! 💚&lt;/p&gt;

&lt;p&gt;Whether you're writing code, improving docs, fixing bugs, or just exploring, you're always welcome!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Small. Simple. Useful. Built Together!&lt;/strong&gt; 🐈&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcs1tfoter0wxpv9enxbh.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcs1tfoter0wxpv9enxbh.gif" alt="Necoz organization" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🐈 Wanna Join Necoz?
&lt;/h2&gt;

&lt;p&gt;We'd love to have you! 😸&lt;/p&gt;

&lt;p&gt;John has already joined us here on DEV! Thanks, &lt;a class="mentioned-user" href="https://dev.to/johnnylemonny"&gt;@johnnylemonny&lt;/a&gt; ! 🎉💚&lt;/p&gt;

&lt;p&gt;If you're interested in joining Necoz here on DEV, just leave a comment below and I'll send you an invitation! 💌&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everyone is welcome!&lt;/strong&gt; 🙌&lt;/p&gt;

&lt;p&gt;You don't need to write articles or contribute anything. Just joining us is more than enough!&lt;/p&gt;

&lt;p&gt;No pressure, no obligations. Let's grow this little community together, one cat at a time! 🐾&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://github.com/necoz-oss" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F350971701%3Fs%3D280%26v%3D4" height="280" class="m-0" width="280"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://github.com/necoz-oss" rel="noopener noreferrer" class="c-link"&gt;
            Necoz · GitHub
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            A friendly open-source community building small, simple, and useful tools together. 😸 - Necoz
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.githubassets.com%2Ffavicons%2Ffavicon.svg" width="32" height="32"&gt;
          github.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Stay a Good Cat! 🐱&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>devto</category>
      <category>community</category>
      <category>organization</category>
      <category>news</category>
    </item>
    <item>
      <title>Stop Generating Changelogs from Git Commits: How I Fixed the 'WHY' Problem in ChangelogBot</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Tue, 03 Nov 2026 16:30:00 +0000</pubDate>
      <link>https://dev.to/necoz/stop-generating-changelogs-from-git-commits-how-i-fixed-the-why-problem-in-changelogbot-11n9</link>
      <guid>https://dev.to/necoz/stop-generating-changelogs-from-git-commits-how-i-fixed-the-why-problem-in-changelogbot-11n9</guid>
      <description>&lt;p&gt;I recently audited a release page for a popular open-source project and found a 140-line changelog that read like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* fix: typo in auth handler (#412)
* refactor: rewrite query builder (#415)
* feat: increase internal timeout to 15s (#418)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;As a developer upgrading that package, those lines tell me almost nothing. &lt;em&gt;Why&lt;/em&gt; was the query builder rewritten? Did it change memory consumption or SQL dialect support? &lt;em&gt;Why&lt;/em&gt; was the timeout tripled? Was there an underlying socket leak under load?&lt;/p&gt;

&lt;p&gt;Generating release notes directly from raw git commits or PR titles is one of the most widespread anti-patterns in modern DevOps. We treat commit logs—which are micro-level implementation breadcrumbs meant for authors—as user-facing documentation.&lt;/p&gt;

&lt;p&gt;In this article, I will explain why traditional automated changelogs fail, introduce &lt;strong&gt;&lt;a href="https://github.com/necoz-oss/changelog-bot" rel="noopener noreferrer"&gt;ChangelogBot&lt;/a&gt;&lt;/strong&gt;—an open-source tool built to extract the actual intent ("WHY") behind changes using AI—and walk through how I re-engineered its GitHub metadata engine to survive secondary rate limits and silent CI failures.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1555066931-4365d14bab8c%3Fauto%3Dformat%26fit%3Dcrop%26w%3D1200%26q%3D80" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1555066931-4365d14bab8c%3Fauto%3Dformat%26fit%3Dcrop%26w%3D1200%26q%3D80" alt="Terminal displaying clean source code on a modern developer workstation" width="1200" height="800"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Mistake: Git Commits Are Not Release Notes
&lt;/h2&gt;

&lt;p&gt;Conventional Commits (&lt;code&gt;feat:&lt;/code&gt;, &lt;code&gt;fix:&lt;/code&gt;, &lt;code&gt;chore:&lt;/code&gt;) solved automated semantic versioning, but they created a massive documentation blind spot.&lt;/p&gt;

&lt;p&gt;When developers open pull requests, the richest context lives in the &lt;strong&gt;PR description, linked issues, and discussion threads&lt;/strong&gt;. That is where engineers explain edge cases, breaking behavioral nuances, and performance trade-offs.&lt;/p&gt;

&lt;p&gt;However, standard release generators throw all of that away:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;They prioritize the &lt;em&gt;what&lt;/em&gt; over the &lt;em&gt;why&lt;/em&gt;:&lt;/strong&gt; A commit title like &lt;code&gt;fix: cache invalidation on tenant switch&lt;/code&gt; describes code motion, not the customer-facing impact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They flood readers with internal noise:&lt;/strong&gt; Squash merges, dependency bumps, and formatting fixes bury critical behavioral changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They force consumers to hunt through PR history:&lt;/strong&gt; If a changelog does not summarize the rationale, developers must click dozens of GitHub links just to evaluate an upgrade.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A real changelog should communicate &lt;strong&gt;intent and impact&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  Enter ChangelogBot: Extracting the "WHY" with AI
&lt;/h2&gt;

&lt;p&gt;To solve this problem, &lt;strong&gt;&lt;a href="https://github.com/necoz-oss/changelog-bot" rel="noopener noreferrer"&gt;ChangelogBot&lt;/a&gt;&lt;/strong&gt; takes a radically different architectural approach:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F965xwmnlq5lxkjrl2388.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F965xwmnlq5lxkjrl2388.png" alt="ChangelogBot Banner" width="799" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of dumping commit messages into Markdown, ChangelogBot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maps release commits to their original GitHub Pull Requests.&lt;/li&gt;
&lt;li&gt;Fetches the PR body, author, and associated discussions.&lt;/li&gt;
&lt;li&gt;Runs a local heuristic trust score to filter out trivial or low-quality descriptions.&lt;/li&gt;
&lt;li&gt;Leverages LLMs (Anthropic Claude or OpenAI) to extract and summarize the architectural &lt;strong&gt;"WHY"&lt;/strong&gt; behind the change.&lt;/li&gt;
&lt;li&gt;Categorizes changes cleanly into human-readable sections, linking back to authors and pull requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is what the difference looks like in practice:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Raw Commit Output (Standard Tools)&lt;/th&gt;
&lt;th&gt;ChangelogBot Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;* fix: listen to published event (#12)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Releases:&lt;/strong&gt; Listen to the &lt;code&gt;published&lt;/code&gt; release event instead of &lt;code&gt;created&lt;/code&gt; (&lt;a href="https://github.com/necoz-oss/changelog-bot/pull/12" rel="noopener noreferrer"&gt;#12&lt;/a&gt;) by &lt;a class="mentioned-user" href="https://dev.to/alice"&gt;@alice&lt;/a&gt;&lt;br&gt;&lt;em&gt;Why: Draft releases can be created first and published later; listening only to creation missed scheduled production deployments.&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;* refactor: mapCommitsToPrs (#45)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;GitHub API:&lt;/strong&gt; Batch commit-to-PR resolutions through a bounded worker pool (&lt;a href="https://github.com/necoz-oss/changelog-bot/pull/45" rel="noopener noreferrer"&gt;#45&lt;/a&gt;) by &lt;a class="mentioned-user" href="https://dev.to/johnnylemonny"&gt;@johnnylemonny&lt;/a&gt;&lt;br&gt;&lt;em&gt;Why: Firing unbounded parallel requests triggered GitHub abuse detection in repositories with over 30 PRs.&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  The Production Trap: Where Release Automation Fails Under Load
&lt;/h2&gt;

&lt;p&gt;When I began contributing to &lt;a href="https://github.com/necoz-oss/changelog-bot" rel="noopener noreferrer"&gt;ChangelogBot&lt;/a&gt;, the core AI extraction worked brilliantly on small repositories. But when I ran it against real production releases containing 40+ merged PRs, the tool hit two critical failure modes in CI/CD.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. The Unbounded &lt;code&gt;Promise.all&lt;/code&gt; Trap (Secondary Rate Limits)
&lt;/h3&gt;

&lt;p&gt;The original commit resolver fetched pull request metadata for every commit in parallel:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ANTI-PATTERN: Firing unbounded requests simultaneously&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;commits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchPRDetails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If a release contained 60 commits, this line fired 60 HTTPS requests to &lt;code&gt;api.github.com&lt;/code&gt; in the exact same millisecond. &lt;/p&gt;

&lt;p&gt;GitHub has a generous primary rate limit (5,000 requests/hour for authenticated users). However, GitHub also enforces &lt;strong&gt;Secondary Rate Limits&lt;/strong&gt; (abuse detection mechanisms designed to prevent concurrent traffic spikes).&lt;/p&gt;

&lt;p&gt;The result? The job failed with &lt;code&gt;HTTP 403 Forbidden&lt;/code&gt; or &lt;code&gt;HTTP 429 Too Many Requests&lt;/code&gt;—even though the token had over 4,800 hourly requests remaining!&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Silent Degradation in CI
&lt;/h3&gt;

&lt;p&gt;The second problem was defensive error handling gone wrong. To prevent crashes, previous code wrapped API lookups in broad &lt;code&gt;try/catch&lt;/code&gt; blocks and returned empty arrays on failure:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// DANGEROUS: Silent fallback hiding network &amp;amp; auth errors&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchPRDetails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commitSha&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="c1"&gt;// CI exits 0, but changelog loses all PR context!&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When GitHub throttled the pipeline or an API token lacked permissions, the bot did not fail. Instead, it silently dropped the PR associations, skipped the LLM enrichment, and generated a degraded changelog containing only bare commit SHAs. The GitHub Action finished with a green checkmark (&lt;code&gt;exit 0&lt;/code&gt;), and the team only noticed the ruined changelog after shipping the release.&lt;/p&gt;


&lt;h2&gt;
  
  
  How I Re-Engineered the Metadata Pipeline
&lt;/h2&gt;

&lt;p&gt;To make ChangelogBot rock-solid for large enterprise repositories, I designed and implemented a three-part architectural fix.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1: Lightweight Bounded Concurrency (&lt;code&gt;mapConcurrent&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;I avoided pulling heavy external dependencies like &lt;code&gt;p-limit&lt;/code&gt; and instead implemented a native, lightweight bounded concurrency worker directly in TypeScript:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Maps items through an asynchronous mapper with a strictly bounded pool of in-flight executions.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mapConcurrent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;mapper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;boundedLimit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;nextIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;workers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boundedLimit&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextIndex&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nextIndex&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;mapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;By capping the concurrency at &lt;code&gt;GITHUB_METADATA_CONCURRENCY = 4&lt;/code&gt;, API lookups remain blisteringly fast while completely eliminating secondary rate-limit triggers.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2: Discriminated Metadata Results
&lt;/h3&gt;

&lt;p&gt;Instead of returning null or empty arrays on failure, I introduced typed result discrimination. Every API interaction now explicitly declares whether it succeeded or degraded, along with a structured failure reason:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;MetadataLookupFailureReason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;not-found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rate-limited&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unauthenticated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invalid-response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;network-error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;MetadataLookupResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;degraded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MetadataLookupFailureReason&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, the engine cleanly distinguishes between &lt;strong&gt;expected missing data&lt;/strong&gt; (like a 404 for a commit that was rebased or cherry-picked) and &lt;strong&gt;critical operational failures&lt;/strong&gt; (such as rate limits or invalid tokens).&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3: Transparent Diagnostic Reporting
&lt;/h3&gt;

&lt;p&gt;I surfaced these metrics directly into the &lt;code&gt;--dry-run&lt;/code&gt; console output and the &lt;code&gt;--dry-run-json-report&lt;/code&gt; schema:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Metadata lookups: total=24, successful=23, degraded=1
Metadata fallback reasons: Commit 3a1b2c4: PR lookup returned 404 (not found)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In automated CI pipelines, teams can inspect the JSON payload and assert that &lt;code&gt;degradedLookups === 0&lt;/code&gt; before publishing a release to production.&lt;/p&gt;


&lt;h2&gt;
  
  
  Quick Start: Using ChangelogBot in Your Project
&lt;/h2&gt;

&lt;p&gt;You can run ChangelogBot locally or wire it directly into your GitHub Actions release workflow.&lt;/p&gt;
&lt;h3&gt;
  
  
  Local Dry-Run
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone and build, or run via npx&lt;/span&gt;
npx @necoz-oss/changelog-bot &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--release-tag&lt;/span&gt; v1.4.0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--release-name&lt;/span&gt; 1.4.0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--provider&lt;/span&gt; anthropic &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  GitHub Actions Workflow
&lt;/h3&gt;

&lt;p&gt;Here is a minimal workflow that automatically updates &lt;code&gt;CHANGELOG.md&lt;/code&gt; and creates a pull request when a release tag is pushed:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update Changelog&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v*'&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;changelog&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;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&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@v4&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;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&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/setup-node@v4&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;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22&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;Generate Changelog with ChangelogBot&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.ANTHROPIC_API_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;REPO_FULL_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.repository }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npx @necoz-oss/changelog-bot \&lt;/span&gt;
            &lt;span class="s"&gt;--release-tag ${{ github.ref_name }} \&lt;/span&gt;
            &lt;span class="s"&gt;--release-name ${{ github.ref_name }} \&lt;/span&gt;
            &lt;span class="s"&gt;--provider anthropic&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;Your changelog is often the only piece of documentation your users and downstream developers read during an upgrade. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stop using commit headers as release notes.&lt;/strong&gt; Raw commits explain file modifications; your users need to understand technical and product intent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never fire unbounded &lt;code&gt;Promise.all&lt;/code&gt; against third-party APIs in CI.&lt;/strong&gt; Secondary rate limits will penalize concurrent bursts even when your hourly quota is full.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Say no to silent &lt;code&gt;try/catch&lt;/code&gt; fallbacks.&lt;/strong&gt; Discriminated union types and structured diagnostics ensure that your automation alerts you when upstream metadata degrades.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Check out the project on GitHub: &lt;strong&gt;&lt;a href="https://github.com/necoz-oss/changelog-bot" rel="noopener noreferrer"&gt;necoz-oss/changelog-bot&lt;/a&gt;&lt;/strong&gt;. Give it a star, test it on your next release, and see what automated release notes look like when they actually explain the "WHY".&lt;/p&gt;



&lt;p&gt;What failure mode has broken an automated release in your CI/CD pipeline—and how did you catch it?&lt;/p&gt;




&lt;h3&gt;
  
  
  Connect with Me
&lt;/h3&gt;

&lt;p&gt;If you found this article helpful, let's connect!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;💻 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/johnnylemonny" rel="noopener noreferrer"&gt;johnnylemonny&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/johnnylemonny" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnfgxlwy9m3uz4zdxzy9b.gif" alt="johnnylemonny GitHub Banner" width="720" height="165"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__3868757"&gt;
    &lt;a href="/johnnylemonny" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3868757%2F1f787f85-4ec3-4a2e-ba8b-097436abcbd2.gif" alt="johnnylemonny image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/johnnylemonny"&gt;𝗝𝗼𝗵𝗻&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/johnnylemonny"&gt;Freelance fullstack developer creating fast, modern, and refined applications. I focus on simplicity, performance, and real value for users. I also write tech-related articles here.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



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