<?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: 𝗝𝗼𝗵𝗻</title>
    <description>The latest articles on DEV Community by 𝗝𝗼𝗵𝗻 (@johnnylemonny).</description>
    <link>https://dev.to/johnnylemonny</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3868757%2F96a5e915-2968-4aeb-8d85-d9206cb3f703.gif</url>
      <title>DEV Community: 𝗝𝗼𝗵𝗻</title>
      <link>https://dev.to/johnnylemonny</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/johnnylemonny"/>
    <language>en</language>
    <item>
      <title>Just received a DEV Contributor badge for suggesting changes to the forem documentation! Hell yeah!</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Tue, 20 Oct 2026 19:09:37 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/just-received-a-dev-contributor-badge-for-suggesting-changes-to-the-forem-documentation-hell-yeah-2cfd</link>
      <guid>https://dev.to/johnnylemonny/just-received-a-dev-contributor-badge-for-suggesting-changes-to-the-forem-documentation-hell-yeah-2cfd</guid>
      <description></description>
      <category>community</category>
      <category>discuss</category>
    </item>
    <item>
      <title>The Hidden Cost of AI-Generated PRs: Why Most Teams Are Missing Silent Breakages</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Tue, 20 Oct 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/the-hidden-cost-of-ai-generated-prs-why-most-teams-are-missing-silent-breakages-3g46</link>
      <guid>https://dev.to/johnnylemonny/the-hidden-cost-of-ai-generated-prs-why-most-teams-are-missing-silent-breakages-3g46</guid>
      <description>&lt;p&gt;Your team is shipping pull requests twice as fast as last year, the CI pipeline is green, and every diff looks cleanly formatted. Yet your production incident rate is quietly climbing.&lt;/p&gt;

&lt;p&gt;According to Google Cloud's &lt;strong&gt;DORA State of AI-Assisted Software Development&lt;/strong&gt; research, while roughly 90% of engineering teams now use AI tools daily to accelerate coding, increased AI adoption without matching quality guardrails correlates directly with a measurable drop in software delivery stability. Code generation is no longer the bottleneck—&lt;strong&gt;code verification is&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When pull request volume doubles while human review capacity stays flat, engineering teams fall into the "LGTM trap": rubber-stamping large, plausible-looking diffs that harbor silent, state-corrupting bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Plausible Code Is Harder to Debug Than Broken Code
&lt;/h2&gt;

&lt;p&gt;When a junior developer writes a bug from scratch, the code usually &lt;em&gt;looks&lt;/em&gt; suspicious. Naming is inconsistent, edge cases are visibly missing, or the syntax feels clunky. A senior reviewer spots the smell in seconds.&lt;/p&gt;

&lt;p&gt;AI-generated code flips this heuristic on its head. Modern coding assistants produce code that is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Syntactically idiomatic and cleanly formatted.&lt;/li&gt;
&lt;li&gt;Accompanied by confident, well-structured PR descriptions.&lt;/li&gt;
&lt;li&gt;Paired with unit tests that pass—because the tests were generated from the exact same flawed implementation context.&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="crayons-card c-embed"&gt;

  &lt;br&gt;
&lt;strong&gt;The Reviewer's Paradox:&lt;/strong&gt; The cleaner and more idiomatic an AI-generated diff looks on the surface, the less cognitive effort a fatigued human reviewer spends tracing its underlying state transitions and failure modes.&lt;br&gt;

&lt;/div&gt;


&lt;h2&gt;
  
  
  The Common Mistake: Local Correctness vs. System Invariants
&lt;/h2&gt;

&lt;p&gt;Coding agents excel at local context (the open file and immediate type definitions), but they frequently miss &lt;strong&gt;system-wide invariants&lt;/strong&gt;—such as idempotency, transactional boundaries, permission scoping, and partial failure recovery.&lt;/p&gt;

&lt;p&gt;Consider a real-world backend scenario: updating a user's subscription tier and syncing billing credits.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Gets Approved in a Hurry (The Silent Bug)
&lt;/h3&gt;

&lt;p&gt;An engineer prompts an assistant to &lt;em&gt;"Add an endpoint handler that upgrades a user's plan and grants bonus credits, with unit tests."&lt;/em&gt; The PR arrives looking clean:&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;// ❌ Looks clean in a 600-line PR diff, but breaks production invariants&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;upgradeSubscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;newPlanId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;credits&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User not found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// 1. External call inside business flow without idempotency key&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;billingClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chargeUpgradeFee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stripeCustomerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newPlanId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Non-atomic read-modify-write (race condition under concurrent requests)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updatedCredits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credits&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Separate DB writes without a transaction&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;planId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newPlanId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;updatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&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="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;credits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;updatedCredits&lt;/span&gt; &lt;span class="p"&gt;},&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="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="s2"&gt;upgraded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;credits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;updatedCredits&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;Why did this pass review?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The TypeScript types compile cleanly.&lt;/li&gt;
&lt;li&gt;The auto-generated unit test mocked &lt;code&gt;billingClient&lt;/code&gt; and &lt;code&gt;db&lt;/code&gt;, asserting that &lt;code&gt;credits&lt;/code&gt; equaled &lt;code&gt;user.credits + 500&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It was buried inside a 14-file PR alongside boilerplate route definitions and DTOs.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  The Consequence in Production
&lt;/h3&gt;

&lt;p&gt;Two weeks later, a client network timeout triggers an automatic HTTP retry:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Double charging:&lt;/strong&gt; &lt;code&gt;billingClient.chargeUpgradeFee&lt;/code&gt; runs twice because no idempotency key was passed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lost updates / Race condition:&lt;/strong&gt; Concurrent requests read the same &lt;code&gt;user.credits&lt;/code&gt; snapshot in memory before writing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partial state corruption:&lt;/strong&gt; If &lt;code&gt;db.users.update&lt;/code&gt; fails due to a transient lock timeout, the user is billed and their plan is updated, but their credits vanish.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The Improved Approach: Designing for Verification, Not Just Generation
&lt;/h2&gt;

&lt;p&gt;Fixing the code requires enforcing transactional boundaries, atomic increments, and idempotency—things a reviewer only catches when diffs are small and architectural constraints are explicit:&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;// ✅ Production-safe: Idempotent, atomic, and transactionally bounded&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;upgradeSubscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;newPlanId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;credits&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="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="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$transaction&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="nx"&gt;tx&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="c1"&gt;// 1. Prevent duplicate execution via idempotency record lock&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existingOp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idempotencyKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existingOp&lt;/span&gt;&lt;span class="p"&gt;)&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;existingOp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responsePayload&lt;/span&gt; &lt;span class="k"&gt;as&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;credits&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="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUniqueOrThrow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Pass deterministic idempotency key to external provider&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;billingClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chargeUpgradeFee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stripeCustomerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newPlanId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;idempotencyKey&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="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;planId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newPlanId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;updatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&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="c1"&gt;// 3. Atomic database increment instead of in-memory calculation&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updatedUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;credits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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="s2"&gt;upgraded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;credits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;updatedUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credits&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idempotencyKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;responsePayload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="p"&gt;},&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;result&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;/p&gt;
  Click to view the hidden test smell that masks AI bugs
  &lt;br&gt;
When an AI assistant writes both the implementation and the test suite in the same prompt, it generates &lt;strong&gt;mirror tests&lt;/strong&gt;—tests that assert &lt;em&gt;what the code does&lt;/em&gt; rather than &lt;em&gt;what the business contract requires&lt;/em&gt;.

&lt;p&gt;If the implementation forgets to wrap two database calls in a transaction, the generated test simply mocks both calls and asserts each was called once. To break this loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write test cases (or failure-mode assertions) &lt;strong&gt;before&lt;/strong&gt; generating implementation code.&lt;/li&gt;
&lt;li&gt;Require at least one test per PR that simulates a partial failure (e.g., the second database write throwing an error) and asserts that state rolls back cleanly.
&lt;/li&gt;
&lt;/ol&gt;



&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Shifting the Workflow: How High-Performing Teams Adapt
&lt;/h2&gt;

&lt;p&gt;As highlighted in Google's engineering practices on &lt;a href="https://google.github.io/eng-practices/review/reviewer/standard.html" rel="noopener noreferrer"&gt;The Standard of Code Review&lt;/a&gt;, the primary purpose of code review is ensuring that the overall code health of the system improves over time. When code generation becomes virtually free, your review standards must tighten, not loosen.&lt;/p&gt;

&lt;p&gt;Use this checklist before opening or approving any AI-assisted pull request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Cap PR size at 250 lines of logic:&lt;/strong&gt; If AI helped you write 800 lines in 20 minutes, split it into three stacked PRs so a human can actually reason about the state transitions.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Separate boilerplate from business logic:&lt;/strong&gt; Never mix auto-generated migrations, DTOs, or formatting changes in the same PR as core domain logic.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Audit the tests before the implementation:&lt;/strong&gt; Check whether the tests verify edge cases and failure rollbacks, or if they merely assert that mocked functions were called.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Check system boundaries first:&lt;/strong&gt; Look directly at external I/O, database transactions, concurrency locks, and authorization checks—where local context models fail most often.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Require the author to explain the failure mode:&lt;/strong&gt; Every PR description should answer one human-written question: &lt;em&gt;"What happens if this code fails halfway through execution?"&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/johnnylemonny" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Explore more production open-source projects on my GitHub&lt;/a&gt;
&lt;/p&gt;
&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;AI coding assistants don't create bad engineering cultures—they amplify your existing bottlenecks. If your team treats pull request velocity as the primary metric of productivity, AI will help you accumulate technical debt and silent production bugs at record speed. Keep batch sizes small, separate test design from implementation generation, and treat human code review as an architectural safeguard rather than a syntax check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the most subtle bug or production failure mode that slipped through an AI-assisted PR in your team recently, and what guardrail did you add to catch it next time?&lt;/strong&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Sources &amp;amp; Further Reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/blog/products/ai-machine-learning/announcing-the-2025-dora-report" rel="noopener noreferrer"&gt;Announcing the 2025 DORA Report: State of AI-Assisted Software Development – Google Cloud Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://google.github.io/eng-practices/review/reviewer/standard.html" rel="noopener noreferrer"&gt;The Standard of Code Review – Google Engineering Practices Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://altersquare.io/blog/pull-requests-ai-adoption-2x-team-bug-count-doubled" rel="noopener noreferrer"&gt;Your Team Ships 2x More Pull Requests Since Adopting AI (GitClear Research Analysis) – AlterSquare&lt;/a&gt;&lt;/li&gt;
&lt;/ul&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;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%2F96a5e915-2968-4aeb-8d85-d9206cb3f703.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>devdiscuss</category>
      <category>productivity</category>
      <category>programming</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Building Mythic Scribe: An RPG Character Generator Powered by Gemini</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Sun, 18 Oct 2026 16:00:00 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/building-mythic-scribe-an-rpg-character-generator-powered-by-gemini-imagen-api-1ckm</link>
      <guid>https://dev.to/johnnylemonny/building-mythic-scribe-an-rpg-character-generator-powered-by-gemini-imagen-api-1ckm</guid>
      <description>&lt;p&gt;&lt;em&gt;This post is my submission for &lt;a href="https://dev.to/deved/build-apps-with-google-ai-studio"&gt;DEV Education Track: Build Apps with Google AI Studio&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built an &lt;strong&gt;RPG Character Portrait &amp;amp; Lore Generator&lt;/strong&gt; using the "Build" feature in Google AI Studio. The application turns user-selected RPG traits (race, class, visual style, and equipment) into a complete character sheet with ability scores, backstory, and a visual portrait. &lt;/p&gt;

&lt;p&gt;It integrates the &lt;strong&gt;Imagen API&lt;/strong&gt; for high-resolution portrait generation alongside &lt;strong&gt;Gemini Flash&lt;/strong&gt; for lore generation. Additionally, because the Imagen API currently requires a billed API key, I implemented an automatic &lt;strong&gt;Canvas Portrait Fallback Generator&lt;/strong&gt; powered by Gemini Flash so the app remains 100% functional and visually engaging even on a free-tier API key.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt Used to Generate the App:
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Build an interactive "RPG Character Portrait &amp;amp; Lore Generator" web application that integrates both the Gemini model for text generation and the Imagen API for image generation.&lt;/p&gt;

&lt;p&gt;App Features &amp;amp; Workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;User Input Form:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Character Name (optional, with a "Randomize" button)&lt;/li&gt;
&lt;li&gt;Race (e.g., Elf, Dwarf, Cyborg, Tiefling, Human, Orc)&lt;/li&gt;
&lt;li&gt;Class/Role (e.g., Necromancer, Paladin, Netrunner, Rogue, Bard)&lt;/li&gt;
&lt;li&gt;Visual Art Style (dropdown: Dark Fantasy Oil Painting, Anime/Cel-Shaded, Pixel Art, Watercolor Concept Art, Cyberpunk Neon)&lt;/li&gt;
&lt;li&gt;Key Traits / Equipment (short text input, e.g., "glowing blue runes on armor, scarred face, holding a crystal staff")&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;AI Generation Logic:&lt;br&gt;
 a) A compelling 3-sentence character backstory and personality trait.&lt;br&gt;
 b) Basic RPG ability scores (Strength, Dexterity, Constitution, Intelligence, Wisdom, Charisma) formatted neatly.&lt;br&gt;
 c) A highly detailed, optimized image generation prompt based on the user's selections.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the Imagen API to generate a high-quality 1:1 character portrait using the optimized visual prompt and selected art style.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;UI/UX Design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dark-mode RPG tavern/grimoire aesthetic (deep slate background, gold/amber accents, clean typography).&lt;/li&gt;
&lt;li&gt;Responsive two-column layout: configuration panel on the left, generated Character Sheet (portrait, stats badges, and backstory) on the right.&lt;/li&gt;
&lt;li&gt;Include loading states while the text and image are being generated, and a button to download the portrait.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Features Utilized:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Build apps with Gemini&lt;/strong&gt; in Google AI Studio for full-stack application generation and iterative refinement.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;Imagen API&lt;/strong&gt; for primary character portrait generation.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini Flash&lt;/strong&gt; for structured RPG lore, stat generation, prompt expansion, and real-time SVG code synthesis as a fallback mechanism.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;🔗 &lt;strong&gt;&lt;a href="https://ai.studio/apps/27506cb8-8916-4b3c-9373-6ff4e59df97c?fullscreenApplet=true" rel="noopener noreferrer"&gt;Link to the deployed app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshot:
&lt;/h3&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%2Fcejnubjmz0npwwoyewx4.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%2Fcejnubjmz0npwwoyewx4.png" alt="Character Sheet and Portrait Generation" width="799" height="382"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Generated RPG character sheet with stats, lore, and custom HTML5 Canvas fallback portrait.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My Experience
&lt;/h2&gt;

&lt;p&gt;Working through this track was a great hands-on introduction to building multimodal web applications rapidly in Google AI Studio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaways &amp;amp; What Surprised Me:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Handling API Tier Constraints Gracefully:&lt;/strong&gt; The biggest surprise during development was discovering that calls to the Imagen API strictly require a paid/billed API key. Instead of letting the app fail with an error for free-tier users, I iterated on the prompt in Google AI Studio to build an automatic HTML5 Canvas portrait fallback generator. When the app detects that Imagen API billing is unavailable, Gemini Flash dynamically writes custom, layered SVG code to render the character's portrait based on their race, class, and equipment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rapid Iteration in AI Studio:&lt;/strong&gt; Adding error handling and a completely secondary visual rendering pipeline (HTML5 Canvas generation to PNG) took just a quick conversational follow-up in the Build tool, showing how effective Gemini is at both UI scaffolding and resilient API integration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Iterative Refinement &amp;amp; Error Recovery:&lt;/strong&gt; The initial scaffold generated by AI Studio felt like a demo and needed polish. The generated text was originally overly complex and bogged down with technical jargon, cluttered labels, and unnecessary descriptions. Through quick follow-up prompts, I stripped out the noise to focus on clean, immersive RPG storytelling. What made this process painless was Gemini’s ability to catch runtime errors, inspect stack traces, and apply automatic fixes on the fly, making iterative UI and prompt tuning remarkably smooth.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>deved</category>
      <category>learngoogleaistudio</category>
      <category>ai</category>
      <category>gemini</category>
    </item>
    <item>
      <title>AI-Generated Tests Can Agree With the Bug</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Thu, 15 Oct 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/ai-generated-tests-can-agree-with-the-bug-2ao8</link>
      <guid>https://dev.to/johnnylemonny/ai-generated-tests-can-agree-with-the-bug-2ao8</guid>
      <description>&lt;p&gt;An AI coding agent implements a feature, generates tests, runs them, and opens a pull request with a green build.&lt;/p&gt;

&lt;p&gt;That looks like independent verification. It often is not.&lt;/p&gt;

&lt;p&gt;If the implementation and the tests came from the same prompt, repository context, and interpretation of the requirement, they can repeat the same mistake. The code is wrong. The tests confidently prove that the wrong behavior works exactly as implemented.&lt;/p&gt;

&lt;p&gt;The failure is not that AI cannot write tests. The failure is treating correlated output as independent evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Test Passed Because It Copied the Assumption
&lt;/h2&gt;

&lt;p&gt;Imagine a billing requirement:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Apply a 20% discount to the order subtotal, then add tax to the discounted amount.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The intended calculation is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;subtotal = 100.00
discount = 20.00
taxable amount = 80.00
tax = 8.00
final total = 88.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;An agent misreads the requirement and calculates tax on the undiscounted subtotal instead of the discounted amount:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subtotal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;discountRate&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;tax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;taxRate&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;discount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;discountRate&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;subtotal&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;tax&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;discount&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;When the same agent generates the unit test in the same session, it applies the exact same faulty mental model (100 + 10 - 20 = 90). The generated test passes:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;calculateTotal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./billing.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;calculateTotal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&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="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;applies a discount and tax&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;calculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;90&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The test looks reasonable at a glance. It uses concrete inputs, asserts a specific number, and produces a green build.&lt;/p&gt;

&lt;p&gt;Because the $2.00 gap (subtotal * taxRate * discountRate) is small, a reviewer skimming the pull request can easily miss that 90 came from the agent's own implementation rather than the requirement (88). The shared assumption becomes a production billing bug.&lt;/p&gt;
&lt;h2&gt;
  
  
  Coverage Is Not an Oracle
&lt;/h2&gt;

&lt;p&gt;Code coverage answers questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Was this line executed?&lt;/li&gt;
&lt;li&gt;Was this branch visited?&lt;/li&gt;
&lt;li&gt;Was this function called?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does not answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Was the expected value correct?&lt;/li&gt;
&lt;li&gt;Did the test encode the product requirement?&lt;/li&gt;
&lt;li&gt;Would the test fail for a plausible wrong implementation?&lt;/li&gt;
&lt;li&gt;Did the system preserve a business invariant?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A generated test suite can reach 100% line coverage while asserting the implementation's current behavior back at itself.&lt;/p&gt;

&lt;p&gt;This pattern is especially weak:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtotal&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtotal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tax&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taxableAmount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tax&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;discount&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The test reconstructs the same formula used by the implementation. If the formula is wrong, both sides remain wrong together.&lt;/p&gt;

&lt;p&gt;A test needs an oracle: an independent reason to believe the expected result is correct.&lt;/p&gt;
&lt;h2&gt;
  
  
  Shared Context Creates Correlated Failures
&lt;/h2&gt;

&lt;p&gt;When one agent writes the code and tests in the same session, both artifacts may inherit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the same ambiguous requirement&lt;/li&gt;
&lt;li&gt;the same incomplete repository context&lt;/li&gt;
&lt;li&gt;the same mistaken domain assumption&lt;/li&gt;
&lt;li&gt;the same missing edge case&lt;/li&gt;
&lt;li&gt;the same naming-driven interpretation&lt;/li&gt;
&lt;li&gt;the same outdated example&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Regenerating the test in the same conversation may not add meaningful independence. The model still sees its earlier reasoning and implementation.&lt;/p&gt;

&lt;p&gt;Even using a second model is not automatically enough. If both models receive the same ambiguous prompt and derive expectations from the same implementation, they can converge on the same attractive mistake.&lt;/p&gt;

&lt;p&gt;The goal is not merely a different generator. The goal is a different source of truth.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Dangerous Test Patterns
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Asserting the implementation against itself
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;formatPrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;formatPrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This extreme example is obvious, but indirect versions are common. A test may call the same helper, parser, mapper, or formula as the production path.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Snapshotting unknown behavior
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;calculateInvoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toMatchSnapshot&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The first snapshot records whatever the implementation produced. Approving it proves that someone accepted the output, not that the output matches the requirement.&lt;/p&gt;

&lt;p&gt;Snapshots are useful for detecting change. They are weak at establishing initial correctness when nobody verifies their meaning.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Mirroring private implementation details
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;internalEntries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The test confirms structure rather than observable behavior. It may pass while users receive stale data, and fail during a safe refactor.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Testing only the example from the prompt
&lt;/h3&gt;

&lt;p&gt;If an issue contains one example, an agent will often reproduce it as one happy-path test. That example may not distinguish competing interpretations.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Mocking away the failure
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;paymentGateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;charge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;mockResolvedValue&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="s2"&gt;paid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The test proves behavior for a dependency that always succeeds instantly. It says nothing about timeouts, duplicated responses, uncertain outcomes, or retries after the remote side processed the charge.&lt;/p&gt;


&lt;strong&gt;A quick review question&lt;/strong&gt;

For every generated assertion, ask:

&amp;gt; Where did this expected value come from?

If the answer is "from the implementation," "from the agent's earlier output," or "from a snapshot we did not inspect," the test is not independent enough for a critical behavior.


&lt;h2&gt;
  
  
  Start With Invariants, Not Examples
&lt;/h2&gt;

&lt;p&gt;Examples verify selected points. Invariants describe properties that must hold across many inputs.&lt;/p&gt;

&lt;p&gt;For a discount calculation, useful invariants might include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a discount cannot increase the pre-tax amount&lt;/li&gt;
&lt;li&gt;a 0% discount changes nothing&lt;/li&gt;
&lt;li&gt;a 100% discount produces no taxable amount&lt;/li&gt;
&lt;li&gt;the total cannot be negative&lt;/li&gt;
&lt;li&gt;changing item order cannot change the total&lt;/li&gt;
&lt;li&gt;rounding happens at the boundary defined by the business rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Property-based tests can exercise these rules across generated inputs:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fast-check&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;calculateTotal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./billing.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;never increases the total when the discount increases&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;double&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;noNaN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;double&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;noNaN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;double&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&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="na"&gt;noNaN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;double&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&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="na"&gt;noNaN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subtotal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;firstDiscount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secondDiscount&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;lower&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;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstDiscount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secondDiscount&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;higher&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="nx"&gt;firstDiscount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secondDiscount&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;totalWithLowerDiscount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;subtotal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;lower&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;totalWithHigherDiscount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;subtotal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;higher&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;totalWithHigherDiscount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeLessThanOrEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;totalWithLowerDiscount&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="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;This still does not prove every domain rule. It does make it harder for one convenient example to hide a broken relationship.&lt;/p&gt;
&lt;h2&gt;
  
  
  Derive Acceptance Tests From a Separate Source
&lt;/h2&gt;

&lt;p&gt;The strongest acceptance tests should come from artifacts that existed independently of the implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;product acceptance criteria&lt;/li&gt;
&lt;li&gt;API contracts&lt;/li&gt;
&lt;li&gt;regulatory rules&lt;/li&gt;
&lt;li&gt;protocol specifications&lt;/li&gt;
&lt;li&gt;previously verified behavior&lt;/li&gt;
&lt;li&gt;production examples approved by domain experts&lt;/li&gt;
&lt;li&gt;compatibility fixtures from another implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the billing example, create a decision table before asking an agent to implement it:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Case: percentage discount before tax
Subtotal: 125.00
Discount: 20%
Tax rate: 8%
Expected discount: 25.00
Expected taxable amount: 100.00
Expected tax: 8.00
Expected total: 108.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then convert that independently reviewed fixture into a test:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;taxes the amount after the percentage discount&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;calculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;125&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.08&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;108&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;Because the expected value (&lt;code&gt;108&lt;/code&gt;) was derived from the specification before the code existed, the test immediately rejects the &lt;code&gt;110&lt;/code&gt; produced by taxing the undiscounted subtotal.&lt;/p&gt;
&lt;h2&gt;
  
  
  Make the Test Fight the Implementation
&lt;/h2&gt;

&lt;p&gt;Mutation testing changes the code in small ways and checks whether the tests fail.&lt;/p&gt;

&lt;p&gt;For example, a mutation tool might replace:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;taxableAmount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;with:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;taxableAmount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;or change:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;freeShippingThreshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;into:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;freeShippingThreshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If the test suite stays green, it did not protect that behavior.&lt;/p&gt;

&lt;p&gt;A mutation score is not a perfect quality metric. Equivalent mutations and costly test runs are real constraints. But targeted mutation testing is valuable for authorization, billing, retries, idempotency, limits, and other logic where a plausible one-character error matters.&lt;/p&gt;

&lt;p&gt;Meta reported a related change-aware approach in its 2026 work on Just-in-Time catching tests. Instead of only generating tests that pass on the proposed code, catching tests are designed to surface regressions before a change lands. In an evaluation of 22,126 generated tests, change-aware methods improved candidate catch generation, and the reported process found confirmed serious failures. The important idea is practical: generated tests should actively try to falsify the change, not merely document it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Use Differential Testing When You Have Two Implementations
&lt;/h2&gt;

&lt;p&gt;Differential testing runs the same inputs through two independent implementations and compares the outputs.&lt;/p&gt;

&lt;p&gt;This is useful during rewrites, migrations, and optimizations:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &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;order&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;generatedOrders&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;legacyResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;legacyCalculator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&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;newResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;newCalculator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newResult&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;legacyResult&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;The reference implementation does not need to be fast or elegant. It needs to be trusted for the behavior under comparison.&lt;/p&gt;

&lt;p&gt;Be careful: if the legacy behavior contains known bugs, encode those exceptions explicitly. Otherwise, differential testing can preserve defects just as snapshots do.&lt;/p&gt;
&lt;h2&gt;
  
  
  Separate the Roles in the Workflow
&lt;/h2&gt;

&lt;p&gt;A safer AI-assisted workflow creates friction between implementation and validation.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1: Extract the contract
&lt;/h3&gt;

&lt;p&gt;Before generating code, write down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;inputs and outputs&lt;/li&gt;
&lt;li&gt;invariants&lt;/li&gt;
&lt;li&gt;boundary conditions&lt;/li&gt;
&lt;li&gt;error behavior&lt;/li&gt;
&lt;li&gt;security constraints&lt;/li&gt;
&lt;li&gt;examples that distinguish plausible interpretations&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Step 2: Generate the implementation
&lt;/h3&gt;

&lt;p&gt;Give the coding agent the contract, but do not let its implementation become the source of expected values.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3: Generate adversarial tests in a fresh context
&lt;/h3&gt;

&lt;p&gt;Provide another agent or session with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the contract&lt;/li&gt;
&lt;li&gt;public interfaces (without the implementation diff, to avoid priming the tester on the author's code structure)&lt;/li&gt;
&lt;li&gt;known failure classes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ask it to find counterexamples that fail on the pre-patch branch first, not to improve coverage.&lt;/p&gt;

&lt;p&gt;A useful prompt is:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Act as an adversarial test engineer.

Given the requirement and public interface:
1. List plausible incorrect interpretations.
2. Create inputs that distinguish them.
3. Identify boundary conditions and invariants.
4. Write tests that would fail for each plausible wrong implementation.
5. Do not derive expected values from the implementation under test.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Step 4: Run targeted mutation testing
&lt;/h3&gt;

&lt;p&gt;Focus on critical decisions rather than mutating the entire repository on every commit.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 5: Review the oracle
&lt;/h3&gt;

&lt;p&gt;Reviewers should spend less time asking whether the test is readable and more time asking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why is this expected value correct?&lt;/li&gt;
&lt;li&gt;Which wrong implementation would this test reject?&lt;/li&gt;
&lt;li&gt;Is the test independent of production helpers?&lt;/li&gt;
&lt;li&gt;What happens at the exact boundary?&lt;/li&gt;
&lt;li&gt;Which requirement remains untested?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Step 6: Keep merge controls
&lt;/h3&gt;

&lt;p&gt;A green agent-generated pull request still needs repository protections, required checks, and human review. GitHub explicitly requires human review before merging draft pull requests produced by its coding agent and layers security scanning and branch restrictions around generated changes.&lt;/p&gt;

&lt;p&gt;That is not ceremony. It is recognition that generated code plus generated tests is evidence, not authority.&lt;/p&gt;
&lt;h2&gt;
  
  
  A Practical Review Example
&lt;/h2&gt;

&lt;p&gt;Suppose an agent adds this rate limiter:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isAllowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestCount&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;requestCount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;limit&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;It also generates:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;allows requests within the limit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isAllowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blocks requests above the limit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isAllowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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;Both tests pass. Coverage may be complete.&lt;/p&gt;

&lt;p&gt;But the critical question is whether the fifth request is allowed or whether &lt;code&gt;limit&lt;/code&gt; represents the number of requests already consumed. The tests avoid the exact boundary where the interpretation matters.&lt;/p&gt;

&lt;p&gt;Add the distinguishing case:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;defines behavior at the exact limit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isAllowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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;Whether &lt;code&gt;false&lt;/code&gt; is correct depends on the contract. The key is that a human or independent specification must decide it. The implementation cannot decide its own requirement.&lt;/p&gt;
&lt;h2&gt;
  
  
  Actionable Checklist
&lt;/h2&gt;

&lt;p&gt;Before trusting AI-generated tests, verify that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Expected values come from a specification, fixture, reference implementation, or reviewed calculation.&lt;/li&gt;
&lt;li&gt;[ ] Tests do not call production helpers to calculate their own expectations.&lt;/li&gt;
&lt;li&gt;[ ] Boundary values are tested explicitly.&lt;/li&gt;
&lt;li&gt;[ ] At least one case distinguishes plausible interpretations of the requirement.&lt;/li&gt;
&lt;li&gt;[ ] Important behavior is expressed as an invariant where possible.&lt;/li&gt;
&lt;li&gt;[ ] Snapshots were inspected for meaning before approval.&lt;/li&gt;
&lt;li&gt;[ ] Mocks include failures, delays, retries, and uncertain outcomes.&lt;/li&gt;
&lt;li&gt;[ ] Generated tests attempt to falsify the change, not only confirm it.&lt;/li&gt;
&lt;li&gt;[ ] Critical logic receives targeted mutation testing.&lt;/li&gt;
&lt;li&gt;[ ] Rewrites are compared against a trusted implementation where appropriate.&lt;/li&gt;
&lt;li&gt;[ ] A reviewer examines the test oracle, not only coverage.&lt;/li&gt;
&lt;li&gt;[ ] Agent-created pull requests remain subject to required checks and human review.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;CTA:&lt;/strong&gt; Pick one AI-generated test from your current project and trace every expected value back to its source. If the trail ends at the implementation itself, replace that assertion with an independent fixture, invariant, or boundary case.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;AI-generated tests are useful. They can produce scaffolding, cover routine branches, explore edge cases, and shorten feedback loops.&lt;/p&gt;

&lt;p&gt;But tests generated from the same context as the implementation are not automatically independent validation.&lt;/p&gt;

&lt;p&gt;They may encode the same misunderstanding twice:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;incorrect requirement interpretation
              ↓
      incorrect implementation
              ↓
     matching incorrect tests
              ↓
          green build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Do not measure confidence only by the number of generated tests or the coverage percentage.&lt;/p&gt;

&lt;p&gt;Increase independence instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;derive expectations from reviewed contracts&lt;/li&gt;
&lt;li&gt;test invariants and exact boundaries&lt;/li&gt;
&lt;li&gt;use fresh adversarial context&lt;/li&gt;
&lt;li&gt;apply mutation and differential testing&lt;/li&gt;
&lt;li&gt;review where each oracle came from&lt;/li&gt;
&lt;li&gt;keep humans responsible for the merge decision&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A test suite is valuable when it can disagree with the implementation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the most convincing green test suite you have seen that still allowed a production bug through?&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://engineering.fb.com/2026/02/11/developer-tools/the-death-of-traditional-testing-agentic-development-jit-testing-revival/" rel="noopener noreferrer"&gt;The Death of Traditional Testing: Agentic Development Broke a 50-Year-Old Field, JiTTesting Can Revive It&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/2601.22832" rel="noopener noreferrer"&gt;Just-in-Time Catching Test Generation at Meta&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/copilot/concepts/security-governance-and-network-settings/risks-and-mitigations" rel="noopener noreferrer"&gt;Risks and mitigations for GitHub Copilot cloud agent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&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;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%2F96a5e915-2968-4aeb-8d85-d9206cb3f703.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>ai</category>
      <category>testing</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Your Metrics Are Lying After They Hit the Cardinality Limit</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Tue, 13 Oct 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/your-metrics-are-lying-after-they-hit-the-cardinality-limit-47mf</link>
      <guid>https://dev.to/johnnylemonny/your-metrics-are-lying-after-they-hit-the-cardinality-limit-47mf</guid>
      <description>&lt;p&gt;Your dashboard says request traffic is normal.&lt;/p&gt;

&lt;p&gt;Your error-rate alert is quiet.&lt;/p&gt;

&lt;p&gt;But users are reporting failures.&lt;/p&gt;

&lt;p&gt;This is not always a bad query or a delayed exporter. Your metrics may have exceeded their cardinality limit, causing OpenTelemetry to preserve the total while silently removing the attributes your dashboards depend on.&lt;/p&gt;

&lt;p&gt;That is a dangerous failure mode because the data still looks believable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem Is Not Request Volume
&lt;/h2&gt;

&lt;p&gt;Metric cardinality is the number of unique attribute combinations recorded for a metric.&lt;/p&gt;

&lt;p&gt;Consider this request counter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;requestCounter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http.route&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/checkout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http.request.method&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;OpenTelemetry maintains aggregation state for each unique combination of attributes.&lt;/p&gt;

&lt;p&gt;These are four different combinations:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ route="/checkout", method="POST", success="true"  }
{ route="/checkout", method="POST", success="false" }
{ route="/cart",     method="GET",  success="true"  }
{ route="/cart",     method="GET",  success="false" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Recording one million requests with the same combination does not create one million metric series.&lt;/p&gt;

&lt;p&gt;Recording one request for each of one million user IDs potentially does.&lt;/p&gt;

&lt;p&gt;The dangerous part is therefore not necessarily request volume. It is how many different attribute combinations those requests produce.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Common Mistake: Treating Metrics Like Logs
&lt;/h2&gt;

&lt;p&gt;A developer wants more context during an incident, so they add a few useful-looking attributes:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;requestCounter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;originalUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This looks helpful. Every measurement contains enough information to identify the exact request.&lt;/p&gt;

&lt;p&gt;It is also a cardinality explosion.&lt;/p&gt;

&lt;p&gt;Values such as these are usually unbounded:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;user IDs&lt;/li&gt;
&lt;li&gt;request IDs&lt;/li&gt;
&lt;li&gt;session IDs&lt;/li&gt;
&lt;li&gt;raw URL paths&lt;/li&gt;
&lt;li&gt;search queries&lt;/li&gt;
&lt;li&gt;email addresses&lt;/li&gt;
&lt;li&gt;exception messages&lt;/li&gt;
&lt;li&gt;dynamically generated resource names&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They can be appropriate in logs or traces, where individual events matter.&lt;/p&gt;

&lt;p&gt;Metrics are designed for aggregation. Their dimensions should normally come from small, predictable sets.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If an attribute helps you find one specific request, it probably belongs in a trace or log, not in a metric.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Raw Paths Are a Frequent Source of Trouble
&lt;/h2&gt;

&lt;p&gt;These URLs may look like one endpoint:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/users/10482/orders/9001
/users/25118/orders/9002
/users/78341/orders/9003
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But as raw metric attribute values, they are three separate combinations.&lt;/p&gt;

&lt;p&gt;The bounded route template is:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/users/{userId}/orders/{orderId}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That difference matters.&lt;/p&gt;

&lt;p&gt;OpenTelemetry distinguishes a raw URL path from a route representing the matched application endpoint. A route template groups requests that execute the same code path, while a raw path can contain identifiers supplied by users.&lt;/p&gt;

&lt;p&gt;A safer measurement looks like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;requestCounter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http.route&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/users/{userId}/orders/{orderId}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http.request.method&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The identifiers can remain on the corresponding span:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttributes&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user.id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;order.id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;orderId&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now metrics answer aggregate questions, while traces preserve request-level context.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Happens When the Limit Is Reached?
&lt;/h2&gt;

&lt;p&gt;OpenTelemetry metrics SDKs use a cardinality limit to prevent unlimited aggregation state from consuming process memory.&lt;/p&gt;

&lt;p&gt;The default aggregation cardinality limit defined by OpenTelemetry is 2,000 combinations per metric stream, although SDK support and configuration details can vary.&lt;/p&gt;

&lt;p&gt;When the limit is reached, additional combinations are aggregated into an overflow data point:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ otel.metric.overflow=true }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The measurement value is not simply discarded.&lt;/p&gt;

&lt;p&gt;The original measurement attributes are.&lt;/p&gt;

&lt;p&gt;Suppose this measurement arrives after the stream reaches its limit:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  route="/checkout",
  tenant_id="tenant-48291",
  success="false"
} 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It may be incorporated into:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ otel.metric.overflow=true } 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Notice what disappeared:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;route&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tenant_id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;success&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The unbounded &lt;code&gt;tenant_id&lt;/code&gt; caused the explosion, but OpenTelemetry does not preserve the other attributes on the overflow point.&lt;/p&gt;

&lt;p&gt;That includes the low-cardinality &lt;code&gt;success&lt;/code&gt; boolean.&lt;/p&gt;
&lt;h2&gt;
  
  
  Your Total Can Be Correct While Your Alert Is Wrong
&lt;/h2&gt;

&lt;p&gt;Imagine the service processed 100,000 requests:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;98,000 successful requests
 2,000 failed requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The total query can still return:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But if 1,400 failed requests were folded into overflow without their original attributes, a filtered query might only see:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;success="false" -&amp;gt; 600
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Your total request count remains correct.&lt;/p&gt;

&lt;p&gt;Your calculated error rate becomes:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;600 / 100000 = 0.6%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The actual error rate is:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2000 / 100000 = 2%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If the alert threshold is 1%, the alert does not fire.&lt;/p&gt;

&lt;p&gt;This can affect any query that filters or groups by measurement attributes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;error-rate alerts&lt;/li&gt;
&lt;li&gt;route-level latency dashboards&lt;/li&gt;
&lt;li&gt;per-tenant SLOs&lt;/li&gt;
&lt;li&gt;status-code breakdowns&lt;/li&gt;
&lt;li&gt;regional traffic analysis&lt;/li&gt;
&lt;li&gt;autoscaling signals&lt;/li&gt;
&lt;li&gt;capacity reports&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is what makes overflow so deceptive. The monitoring system continues returning numbers. Only some dimensions have become incomplete.&lt;/p&gt;
&lt;h2&gt;
  
  
  Cardinality Multiplies Faster Than It Looks
&lt;/h2&gt;

&lt;p&gt;Assume a metric contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;120 routes&lt;/li&gt;
&lt;li&gt;5 HTTP methods&lt;/li&gt;
&lt;li&gt;6 status-code groups&lt;/li&gt;
&lt;li&gt;2 success values&lt;/li&gt;
&lt;li&gt;4 deployment regions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The theoretical maximum is:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;120 × 5 × 6 × 2 × 4 = 28,800 combinations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Not every combination will necessarily occur, but this calculation exposes the risk.&lt;/p&gt;

&lt;p&gt;Now add 10,000 tenants:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;28,800 × 10,000 = 288,000,000 combinations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Adding “just one label” can change the metric from bounded to effectively unbounded.&lt;/p&gt;

&lt;p&gt;Histograms deserve additional attention because their exported representation can produce multiple time series per attribute combination, depending on the aggregation and backend.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  Why local testing rarely reveals this problem
  &lt;p&gt;Development traffic usually exercises:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a small number of routes&lt;/li&gt;
&lt;li&gt;one or two users&lt;/li&gt;
&lt;li&gt;a few tenants&lt;/li&gt;
&lt;li&gt;short process lifetimes&lt;/li&gt;
&lt;li&gt;limited parallel activity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Production introduces the full value space. A metric may behave correctly in tests and staging, then overflow only after enough unique combinations have accumulated.&lt;/p&gt;

&lt;p&gt;With cumulative temporality, a process can retain combinations across collection cycles. A long-running production instance may therefore hit a limit that a frequently restarted staging instance never reaches.&lt;/p&gt;



&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Detect Overflow Before Debugging the Dashboard
&lt;/h2&gt;

&lt;p&gt;Overflow points use this attribute:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;otel.metric.overflow=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Prometheus-compatible systems often normalize dots to underscores, exposing it as:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;otel_metric_overflow="true"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A PromQL query for recent overflow can look 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;count by (__name__, job) (
  last_over_time(
    {otel_metric_overflow="true"}[5m]
  )
) &amp;gt; 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Adjust resource labels such as &lt;code&gt;job&lt;/code&gt;, &lt;code&gt;service_name&lt;/code&gt;, &lt;code&gt;cluster&lt;/code&gt;, or &lt;code&gt;namespace&lt;/code&gt; to match your environment.&lt;/p&gt;

&lt;p&gt;For an OTLP-native backend, use its query language to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;filter on &lt;code&gt;otel.metric.overflow = true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;group by metric name&lt;/li&gt;
&lt;li&gt;group by service name&lt;/li&gt;
&lt;li&gt;identify the first deployment or time window where overflow appeared&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not treat an empty result as absolute proof that cardinality is safe. Confirm that your SDK implements the relevant limit and that your exporter and backend retain the overflow attribute.&lt;/p&gt;
&lt;h2&gt;
  
  
  Do Not Fix It by Blindly Raising the Limit
&lt;/h2&gt;

&lt;p&gt;Increasing the limit can stop overflow temporarily.&lt;/p&gt;

&lt;p&gt;It can also allow substantially more aggregation state to accumulate in application memory and push more expensive series into your observability backend.&lt;/p&gt;

&lt;p&gt;A higher limit is appropriate when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every attribute is intentional&lt;/li&gt;
&lt;li&gt;the dimensions are bounded&lt;/li&gt;
&lt;li&gt;the expected number of combinations is known&lt;/li&gt;
&lt;li&gt;the process has sufficient memory&lt;/li&gt;
&lt;li&gt;the backend can handle the resulting series&lt;/li&gt;
&lt;li&gt;overflow is continuously monitored&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not the right first response to a request ID, raw path, or exception message appearing in a metric.&lt;/p&gt;

&lt;p&gt;Fix the attribute model first.&lt;/p&gt;
&lt;h2&gt;
  
  
  Build a Cardinality Budget
&lt;/h2&gt;

&lt;p&gt;Before shipping a metric, list every attribute and estimate its value space.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http.route             80 known values
http.request.method     5 known values
status_class             5 known values
region                   3 known values
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A pessimistic upper bound is:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;80 × 5 × 5 × 3 = 6,000 combinations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This does not mean all 6,000 combinations will exist simultaneously. It gives you a useful boundary to challenge before deployment.&lt;/p&gt;

&lt;p&gt;Ask these questions for every attribute:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is its value space bounded?&lt;/li&gt;
&lt;li&gt;Can a user create new values?&lt;/li&gt;
&lt;li&gt;Does it contain an identifier?&lt;/li&gt;
&lt;li&gt;Can it include raw input?&lt;/li&gt;
&lt;li&gt;Do we actually group or filter by it?&lt;/li&gt;
&lt;li&gt;Would the value be more useful on a span or log?&lt;/li&gt;
&lt;li&gt;What happens if one process sees every possible value?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you cannot estimate the value space, treat the attribute as high-cardinality until proven otherwise.&lt;/p&gt;
&lt;h2&gt;
  
  
  Use Views to Remove Dangerous Attributes
&lt;/h2&gt;

&lt;p&gt;OpenTelemetry Views can customize metric streams, including which attributes are retained.&lt;/p&gt;

&lt;p&gt;The exact API differs between SDK languages, but the intended transformation is simple:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input attributes:
  http.route
  http.request.method
  http.response.status_code
  user_id
  request_id

Retained metric attributes:
  http.route
  http.request.method
  http.response.status_code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The identifiers remain available in traces or logs.&lt;/p&gt;

&lt;p&gt;The metric keeps only dimensions that are useful for aggregation.&lt;/p&gt;

&lt;p&gt;This separation gives you both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;affordable, reliable aggregate monitoring&lt;/li&gt;
&lt;li&gt;detailed context when investigating individual requests&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Prefer Categories Over Raw Values
&lt;/h2&gt;

&lt;p&gt;Sometimes the problematic attribute contains useful information but has too many possible values.&lt;/p&gt;

&lt;p&gt;Replace the raw value with a bounded category.&lt;/p&gt;

&lt;p&gt;Instead of recording the full exception message:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;errorCounter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;error_message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Classify the failure:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;errorCounter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;error_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;classifyError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;Where the classifier returns a controlled set:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;classifyError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;TimeoutError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeout&lt;/span&gt;&lt;span class="dl"&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;validation&lt;/span&gt;&lt;span class="dl"&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;DependencyError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dependency&lt;/span&gt;&lt;span class="dl"&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;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;"&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;The complete error message belongs in a log or span event:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recordException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The metric answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Are timeout failures increasing?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The trace answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What happened to this request?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Trying to make one telemetry signal answer both questions is what creates the problem.&lt;/p&gt;
&lt;h2&gt;
  
  
  Add Overflow to Your Observability Tests
&lt;/h2&gt;

&lt;p&gt;Instrumentation deserves tests just like business logic.&lt;/p&gt;

&lt;p&gt;A useful test should generate many unique values and verify that the exported metric does not retain an unbounded attribute.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &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;index&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="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;index&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;requestCounter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/users/&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`request-&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;The test should fail if &lt;code&gt;route&lt;/code&gt; contains raw identifiers or if &lt;code&gt;request_id&lt;/code&gt; reaches the exported metric stream.&lt;/p&gt;

&lt;p&gt;A corrected version records a normalized route:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &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;index&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="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;index&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;requestCounter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http.route&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/users/{userId}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;You can also add CI checks that inspect instrumentation changes for suspicious attribute names:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user_id
request_id
session_id
email
query
url.path
error.message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is not a perfect detector, but it forces deliberate review before an unbounded dimension reaches production.&lt;/p&gt;
&lt;h2&gt;
  
  
  Production Checklist
&lt;/h2&gt;

&lt;p&gt;Before deploying a new or modified metric:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] List every metric attribute.&lt;/li&gt;
&lt;li&gt;[ ] Estimate the number of possible values for each attribute.&lt;/li&gt;
&lt;li&gt;[ ] Calculate a pessimistic combination count.&lt;/li&gt;
&lt;li&gt;[ ] Replace raw paths with route templates.&lt;/li&gt;
&lt;li&gt;[ ] Keep request, user, and session IDs out of metrics.&lt;/li&gt;
&lt;li&gt;[ ] Replace raw error messages with bounded error categories.&lt;/li&gt;
&lt;li&gt;[ ] Move request-level context to traces or logs.&lt;/li&gt;
&lt;li&gt;[ ] Verify the SDK's cardinality-limit behavior.&lt;/li&gt;
&lt;li&gt;[ ] Query for &lt;code&gt;otel.metric.overflow=true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] Alert on overflow for metrics used by SLOs or paging.&lt;/li&gt;
&lt;li&gt;[ ] Review cumulative versus delta temporality.&lt;/li&gt;
&lt;li&gt;[ ] Raise the limit only after validating the attribute model.&lt;/li&gt;
&lt;li&gt;[ ] Load-test instrumentation with realistic value diversity.&lt;/li&gt;
&lt;li&gt;[ ] Recheck cardinality after framework or instrumentation upgrades.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Before changing the limit:&lt;/strong&gt; Review the &lt;a href="https://opentelemetry.io/docs/concepts/signals/metrics/" rel="noopener noreferrer"&gt;OpenTelemetry cardinality-limit documentation&lt;/a&gt; and verify that the metric attributes are intentionally bounded.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;A cardinality limit protects your application from unbounded metric state.&lt;/p&gt;

&lt;p&gt;It does not guarantee that every dashboard, SLO, or alert remains accurate after overflow.&lt;/p&gt;

&lt;p&gt;Once measurements are folded into an overflow point:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;totals can remain correct&lt;/li&gt;
&lt;li&gt;attributes can disappear&lt;/li&gt;
&lt;li&gt;filtered queries can undercount&lt;/li&gt;
&lt;li&gt;error alerts can stay quiet&lt;/li&gt;
&lt;li&gt;dashboards can continue looking believable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The safest approach is not to maximize the limit. It is to design metric dimensions deliberately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use bounded categories&lt;/li&gt;
&lt;li&gt;normalize routes&lt;/li&gt;
&lt;li&gt;keep identifiers in traces and logs&lt;/li&gt;
&lt;li&gt;define a cardinality budget&lt;/li&gt;
&lt;li&gt;monitor overflow as a production failure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your monitoring should never fail silently while continuing to display convincing numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which high-cardinality attribute has caused the hardest observability problem in one of your production systems?&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://opentelemetry.io/blog/2026/cardinality-limits-in-opentelemetry/index.md" rel="noopener noreferrer"&gt;Metric cardinality limits in OpenTelemetry: a practical guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opentelemetry.io/docs/concepts/signals/metrics/" rel="noopener noreferrer"&gt;OpenTelemetry Metrics: Cardinality Limits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opentelemetry.io/docs/zero-code/obi/cardinality/" rel="noopener noreferrer"&gt;OpenTelemetry OBI Metrics Cardinality&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/" rel="noopener noreferrer"&gt;OpenTelemetry Attribute Requirement Levels&lt;/a&gt;&lt;/li&gt;
&lt;/ul&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;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%2F96a5e915-2968-4aeb-8d85-d9206cb3f703.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>opentelemetry</category>
      <category>observability</category>
      <category>devops</category>
      <category>performance</category>
    </item>
    <item>
      <title>Honored by the shoutout from @nyaomaru! We broke down how changelog-bot extracts the "Why" behind PRs: treating it as evidence selection over source text, backed by our 50-case evaluation harness. Check out the writeup!</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Sat, 10 Oct 2026 16:50:39 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/honored-by-the-shoutout-from-nyaomaru-we-broke-down-how-changelog-bot-extracts-the-why-behind-53a4</link>
      <guid>https://dev.to/johnnylemonny/honored-by-the-shoutout-from-nyaomaru-we-broke-down-how-changelog-bot-extracts-the-why-behind-53a4</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nyaomaru/how-do-we-extract-the-why-from-a-pr-into-a-changelog-with-jev-52pk" class="crayons-story__hidden-navigation-link"&gt;How Do We Extract the “Why” from a PR into a CHANGELOG with Jev? 🤔&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/nyaomaru/how-do-we-extract-the-why-from-a-pr-into-a-changelog-with-jev-52pk" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Balancing strict LLM constraints with raw PR text&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/nyaomaru" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" alt="nyaomaru profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nyaomaru" class="crayons-story__secondary fw-medium m:hidden"&gt;
              nyaomaru
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                nyaomaru
                
                
              
              &lt;div id="story-author-preview-content-4819360" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nyaomaru" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;nyaomaru&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/nyaomaru/how-do-we-extract-the-why-from-a-pr-into-a-changelog-with-jev-52pk" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Oct 10&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/nyaomaru/how-do-we-extract-the-why-from-a-pr-into-a-changelog-with-jev-52pk" id="article-link-4819360"&gt;
          How Do We Extract the “Why” from a PR into a CHANGELOG with Jev? 🤔
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/githubactions"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;githubactions&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/nyaomaru/how-do-we-extract-the-why-from-a-pr-into-a-changelog-with-jev-52pk" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;114&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/nyaomaru/how-do-we-extract-the-why-from-a-pr-into-a-changelog-with-jev-52pk#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              60&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>ai</category>
      <category>automation</category>
      <category>github</category>
      <category>llm</category>
    </item>
    <item>
      <title>Your Page Loaded Fast. So Why Does It Still Feel Slow?</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Thu, 08 Oct 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/your-page-loaded-fast-so-why-does-it-still-feel-slow-3npm</link>
      <guid>https://dev.to/johnnylemonny/your-page-loaded-fast-so-why-does-it-still-feel-slow-3npm</guid>
      <description>&lt;p&gt;The page appears in less than a second.&lt;/p&gt;

&lt;p&gt;The user clicks a button.&lt;/p&gt;

&lt;p&gt;Nothing happens.&lt;/p&gt;

&lt;p&gt;The content was visible, the network request had finished, and the load report looked respectable. The main thread was simply too busy parsing, executing, and hydrating JavaScript to respond.&lt;/p&gt;

&lt;p&gt;A page can load quickly and still feel slow.&lt;/p&gt;

&lt;p&gt;If you optimize only the first render, you may be measuring the moment the interface becomes visible rather than the moment it becomes usable.&lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  

&lt;p&gt;&lt;strong&gt;Visible is not the same as interactive.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Measure what happens after the user clicks, types, opens a menu, or submits a form. A fast screenshot does not guarantee a responsive application.&lt;/p&gt;


&lt;/div&gt;


&lt;h2&gt;
  
  
  Start with a real interaction
&lt;/h2&gt;

&lt;p&gt;Do not begin with a bundle-size target.&lt;/p&gt;

&lt;p&gt;Begin with an action a user actually performs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;opening the main navigation,&lt;/li&gt;
&lt;li&gt;typing into search,&lt;/li&gt;
&lt;li&gt;applying a filter,&lt;/li&gt;
&lt;li&gt;expanding a product panel,&lt;/li&gt;
&lt;li&gt;adding an item to a cart,&lt;/li&gt;
&lt;li&gt;submitting a form.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then record what the user experiences:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Click
  ↓
Visible response begins
  ↓
Interface finishes updating
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If there is a noticeable pause after the input, the page has a responsiveness problem even when initial content appeared quickly.&lt;/p&gt;

&lt;p&gt;Interaction to Next Paint, or INP, measures the latency of user interactions across a page visit. Web performance guidance recommends an INP of 200 milliseconds or less at the 75th percentile for a good user experience.&lt;/p&gt;

&lt;p&gt;Field data is the strongest evidence because it reflects real devices, networks, and interactions. Lab tools help reproduce and explain the problem.&lt;/p&gt;
&lt;h2&gt;
  
  
  Find long tasks in the Performance panel
&lt;/h2&gt;

&lt;p&gt;Open browser DevTools, switch to the &lt;strong&gt;Performance&lt;/strong&gt; panel, and record the interaction that feels slow.&lt;/p&gt;

&lt;p&gt;During the recording:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;wait for the page to settle,&lt;/li&gt;
&lt;li&gt;click or type exactly as a user would,&lt;/li&gt;
&lt;li&gt;stop recording after the interface updates,&lt;/li&gt;
&lt;li&gt;inspect the main-thread timeline around the interaction.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A long task is an uninterrupted period where the main UI thread stays busy for at least 50 milliseconds. While that task runs, the browser cannot process another main-thread task, including input handling and rendering work.&lt;/p&gt;

&lt;p&gt;Common causes include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;expensive event handlers,&lt;/li&gt;
&lt;li&gt;parsing and evaluating large scripts,&lt;/li&gt;
&lt;li&gt;synchronous data processing,&lt;/li&gt;
&lt;li&gt;repeated rendering,&lt;/li&gt;
&lt;li&gt;forced layout,&lt;/li&gt;
&lt;li&gt;third-party scripts,&lt;/li&gt;
&lt;li&gt;hydration work,&lt;/li&gt;
&lt;li&gt;initializing features not yet needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Chromium-based DevTools, long tasks are marked visually in the performance trace. Expand the task and inspect the call tree or bottom-up view to find the functions consuming time.&lt;/p&gt;

&lt;p&gt;Do not optimize the first large function name you see without checking the complete stack. Framework code may be executing because application code requested too much work.&lt;/p&gt;
&lt;h2&gt;
  
  
  Measure JavaScript, not only transfer size
&lt;/h2&gt;

&lt;p&gt;Compression can make a script small on the network while leaving substantial work for the browser.&lt;/p&gt;

&lt;p&gt;JavaScript must still be:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Downloaded
   ↓
Decompressed
   ↓
Parsed
   ↓
Compiled
   ↓
Executed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A package that adds only a modest compressed payload can still perform expensive initialization, walk a large DOM, register many listeners, or schedule repeated work.&lt;/p&gt;

&lt;p&gt;Use the Network panel to understand transfer size, but use the Performance panel to understand main-thread cost.&lt;/p&gt;

&lt;p&gt;Ask these questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this script needed before the first interaction?&lt;/li&gt;
&lt;li&gt;Is the complete library used?&lt;/li&gt;
&lt;li&gt;Does initialization run for components not present on the page?&lt;/li&gt;
&lt;li&gt;Does the script execute during every navigation?&lt;/li&gt;
&lt;li&gt;Is the same calculation repeated?&lt;/li&gt;
&lt;li&gt;Is third-party code running at the moment of input?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Remove work before splitting work
&lt;/h2&gt;

&lt;p&gt;Code splitting is useful, but the best delayed JavaScript is often JavaScript the page no longer needs.&lt;/p&gt;

&lt;p&gt;Look for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unused libraries,&lt;/li&gt;
&lt;li&gt;duplicated utilities,&lt;/li&gt;
&lt;li&gt;obsolete analytics integrations,&lt;/li&gt;
&lt;li&gt;polyfills outside the supported browser policy,&lt;/li&gt;
&lt;li&gt;large dependencies used for one small helper,&lt;/li&gt;
&lt;li&gt;components loaded globally but rendered rarely,&lt;/li&gt;
&lt;li&gt;initialization that can be replaced with HTML or CSS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, do not load a charting library on every page if only one route displays a chart.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;renderChart&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="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./chart.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;renderChart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Dynamic import moves the module behind the point where it becomes necessary.&lt;/p&gt;

&lt;p&gt;That improves responsiveness only if the feature genuinely does not need the code earlier. Moving required work directly into the click handler may replace load delay with interaction delay.&lt;/p&gt;

&lt;p&gt;A better option can be to load an optional feature during idle time or after primary content becomes interactive.&lt;/p&gt;
&lt;h2&gt;
  
  
  Break long work into smaller tasks
&lt;/h2&gt;

&lt;p&gt;Sometimes the work is necessary but does not need to block the main thread in one uninterrupted chunk.&lt;/p&gt;

&lt;p&gt;Imagine processing a large collection:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processAllItems&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &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;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;processItem&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="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 the loop becomes a long task, yield periodically:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processItems&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &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;index&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="nx"&gt;index&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="nx"&gt;index&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;processItem&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;index&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;100&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="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;yield&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Yielding gives the browser an opportunity to process higher-priority work such as input and rendering before continuing.&lt;/p&gt;

&lt;p&gt;The best chunk size depends on the cost of each item and target devices. A fixed item count is easy to demonstrate but should be measured in the real application.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;scheduler.yield()&lt;/code&gt; is not available in your browser support range, use an appropriate scheduling fallback and keep the behavior progressively enhanced.&lt;/p&gt;
&lt;h3&gt;
  
  
  Move CPU-heavy work off the main thread
&lt;/h3&gt;

&lt;p&gt;For expensive calculations that do not need direct DOM access, consider a Web Worker.&lt;/p&gt;

&lt;p&gt;Good candidates include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;parsing large datasets,&lt;/li&gt;
&lt;li&gt;image manipulation,&lt;/li&gt;
&lt;li&gt;compression,&lt;/li&gt;
&lt;li&gt;complex search indexing,&lt;/li&gt;
&lt;li&gt;numerical calculations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Workers add message-passing and lifecycle complexity. Use them when measurement shows CPU work is blocking interactions, not as a default wrapper for every function.&lt;/p&gt;
&lt;h2&gt;
  
  
  Keep interaction handlers focused
&lt;/h2&gt;

&lt;p&gt;An event handler should perform the minimum work required to produce the next useful visual response.&lt;/p&gt;

&lt;p&gt;This handler does too much synchronously:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&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="nf"&gt;calculateLargeReport&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;storeAnalyticsPayload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;updateRecommendations&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;openPanel&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;The user waits for unrelated work before seeing the panel.&lt;/p&gt;

&lt;p&gt;Prioritize the visible response:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&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="nf"&gt;openPanel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;setTimeout&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="nf"&gt;calculateLargeReport&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;storeAnalyticsPayload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;updateRecommendations&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;0&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;This simplified example separates tasks, but &lt;code&gt;setTimeout(..., 0)&lt;/code&gt; does not make expensive work cheap. If the deferred work is still large, split it further, move it to a worker, or remove it.&lt;/p&gt;

&lt;p&gt;Also avoid reading layout information immediately after changing styles when possible. Repeated read-write cycles can force synchronous layout:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Repeated read/write pattern&lt;/span&gt;
&lt;span class="k"&gt;for &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;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;items&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;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetWidth&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&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;Read shared measurements once, then apply updates:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetWidth&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;for &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;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;items&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;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&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;h2&gt;
  
  
  Audit third-party scripts
&lt;/h2&gt;

&lt;p&gt;Your application is not the only code using the main thread.&lt;/p&gt;

&lt;p&gt;Analytics, experimentation, advertising, chat, support widgets, personalization, and tag managers can all execute around user interactions.&lt;/p&gt;

&lt;p&gt;For each third-party script, document:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;who owns it,&lt;/li&gt;
&lt;li&gt;why it exists,&lt;/li&gt;
&lt;li&gt;where it loads,&lt;/li&gt;
&lt;li&gt;whether it is required on every page,&lt;/li&gt;
&lt;li&gt;how much main-thread time it consumes,&lt;/li&gt;
&lt;li&gt;what user data it accesses,&lt;/li&gt;
&lt;li&gt;what happens when it fails.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remove scripts without a current owner or measurable purpose.&lt;/p&gt;

&lt;p&gt;Load nonessential tools after the primary experience is usable. Be careful with a tag manager that can silently restore removed scripts later.&lt;/p&gt;

&lt;p&gt;A third-party script may not be large enough to dominate network transfer and can still create long tasks through initialization or repeated callbacks.&lt;/p&gt;
&lt;h2&gt;
  
  
  Watch for hydration delays
&lt;/h2&gt;

&lt;p&gt;Server-rendered HTML can appear before client-side JavaScript finishes attaching behavior.&lt;/p&gt;

&lt;p&gt;That creates a frustrating state:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The button is visible
The button looks interactive
The click handler is not ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To reduce the gap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ship less client-side JavaScript,&lt;/li&gt;
&lt;li&gt;avoid hydrating static sections,&lt;/li&gt;
&lt;li&gt;isolate interactive components,&lt;/li&gt;
&lt;li&gt;defer low-priority widgets,&lt;/li&gt;
&lt;li&gt;keep initial client state small,&lt;/li&gt;
&lt;li&gt;prefer server or platform behavior when interaction is unnecessary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Framework-specific tools differ, but the principle is stable: do not make the browser hydrate parts of the page that never needed client-side ownership.&lt;/p&gt;
&lt;h2&gt;
  
  
  Verify the improvement
&lt;/h2&gt;

&lt;p&gt;Repeat the same interaction under the same conditions.&lt;/p&gt;

&lt;p&gt;Compare:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before
- interaction delay
- longest task
- total main-thread work
- JavaScript evaluation time

After
- interaction delay
- longest task
- total main-thread work
- JavaScript evaluation time
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Use CPU throttling to reveal problems hidden by a fast development machine. Then verify with field data, because real users have different devices, extensions, background apps, and interaction patterns.&lt;/p&gt;

&lt;p&gt;A lower bundle size is useful evidence, but it is not proof that the slow interaction is fixed.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  Responsiveness audit checklist
  &lt;ul&gt;
&lt;li&gt;[ ] Start with a real click, tap, or keypress.&lt;/li&gt;
&lt;li&gt;[ ] Check field INP data when available.&lt;/li&gt;
&lt;li&gt;[ ] Record the interaction in the Performance panel.&lt;/li&gt;
&lt;li&gt;[ ] Find long tasks around the input.&lt;/li&gt;
&lt;li&gt;[ ] Inspect the call stack before changing code.&lt;/li&gt;
&lt;li&gt;[ ] Remove unnecessary JavaScript first.&lt;/li&gt;
&lt;li&gt;[ ] Delay optional features away from critical interactions.&lt;/li&gt;
&lt;li&gt;[ ] Split necessary long work into smaller tasks.&lt;/li&gt;
&lt;li&gt;[ ] Move suitable CPU work to a Web Worker.&lt;/li&gt;
&lt;li&gt;[ ] Audit third-party scripts.&lt;/li&gt;
&lt;li&gt;[ ] Check for visible-but-not-hydrated controls.&lt;/li&gt;
&lt;li&gt;[ ] Reproduce the same interaction after the change.&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Initial load speed and interaction speed are related, but they are not the same problem.&lt;/p&gt;

&lt;p&gt;A responsive page needs room on the main thread for user input and rendering.&lt;/p&gt;

&lt;p&gt;The practical workflow is:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Choose a real interaction
          ↓
Record it in DevTools
          ↓
Find the blocking task
          ↓
Remove unnecessary work
          ↓
Delay, split, or move necessary work
          ↓
Repeat the same interaction
          ↓
Verify with field data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Do not optimize only the moment the page becomes visible.&lt;/p&gt;

&lt;p&gt;Optimize the moment the user tries to use it.&lt;/p&gt;

&lt;p&gt;Which interaction in your application feels slower than the initial page load?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://web.dev/articles/optimize-inp" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Read the web.dev guide to optimizing INP&lt;/a&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Sources and further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MDN:&lt;/strong&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Performance/JavaScript" rel="noopener noreferrer"&gt;JavaScript performance optimization&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MDN:&lt;/strong&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceLongTaskTiming" rel="noopener noreferrer"&gt;PerformanceLongTaskTiming&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;web.dev:&lt;/strong&gt; &lt;a href="https://web.dev/articles/optimize-long-tasks" rel="noopener noreferrer"&gt;Optimize long tasks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;web.dev:&lt;/strong&gt; &lt;a href="https://web.dev/articles/optimize-inp" rel="noopener noreferrer"&gt;Optimize Interaction to Next Paint&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;W3C:&lt;/strong&gt; &lt;a href="https://www.w3.org/TR/longtasks-1/" rel="noopener noreferrer"&gt;Long Tasks API&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&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;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%2F96a5e915-2968-4aeb-8d85-d9206cb3f703.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>webdev</category>
      <category>javascript</category>
      <category>performance</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Stop Retrying Every Failed Request: Your Resilience Code May Be Causing the Outage</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Tue, 06 Oct 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/stop-retrying-every-failed-request-your-resilience-code-may-be-causing-the-outage-32a0</link>
      <guid>https://dev.to/johnnylemonny/stop-retrying-every-failed-request-your-resilience-code-may-be-causing-the-outage-32a0</guid>
      <description>&lt;p&gt;The API returns &lt;code&gt;503 Service Unavailable&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Your client immediately retries three times.&lt;/p&gt;

&lt;p&gt;That sounds resilient.&lt;/p&gt;

&lt;p&gt;Now imagine ten thousand clients doing the same thing at the same time.&lt;/p&gt;

&lt;p&gt;The struggling service does not receive less traffic. It receives several times more traffic precisely when it has the least capacity to handle it.&lt;/p&gt;

&lt;p&gt;A retry is not automatically a recovery mechanism.&lt;/p&gt;

&lt;p&gt;Without timeouts, backoff, jitter, retry limits, and safe operation semantics, retry code can turn a temporary failure into a longer outage.&lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  

&lt;p&gt;&lt;strong&gt;Retry only when all three conditions are true:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The failure is likely to be temporary.&lt;/li&gt;
&lt;li&gt;Repeating the operation is safe.&lt;/li&gt;
&lt;li&gt;The next attempt will not happen immediately.&lt;/li&gt;
&lt;/ol&gt;


&lt;/div&gt;


&lt;h2&gt;
  
  
  The retry loop that makes failures worse
&lt;/h2&gt;

&lt;p&gt;This implementation looks reasonable at first glance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;requestWithRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &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;attempt&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="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="o"&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;try&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;response&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&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;response&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;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Try again immediately&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Request failed&lt;/span&gt;&lt;span class="dl"&gt;"&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;It has several problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every attempt can wait indefinitely,&lt;/li&gt;
&lt;li&gt;all failures are treated as retryable,&lt;/li&gt;
&lt;li&gt;retries happen immediately,&lt;/li&gt;
&lt;li&gt;every client follows the same timing,&lt;/li&gt;
&lt;li&gt;the total operation has no time budget,&lt;/li&gt;
&lt;li&gt;the method does not consider whether repeating the request is safe,&lt;/li&gt;
&lt;li&gt;the final error loses useful context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix is not simply “add more retries.”&lt;/p&gt;

&lt;p&gt;The fix is to make every retry earn its place.&lt;/p&gt;
&lt;h2&gt;
  
  
  Start with a timeout
&lt;/h2&gt;

&lt;p&gt;A request that never completes can occupy connections, memory, and other limited resources. Set a maximum wait for each attempt.&lt;/p&gt;

&lt;p&gt;Modern JavaScript provides &lt;code&gt;AbortSignal.timeout()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AbortSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="nx"&gt;_000&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;The signal aborts the request after the configured active time and rejects with a &lt;code&gt;TimeoutError&lt;/code&gt; DOM exception.&lt;/p&gt;

&lt;p&gt;If the caller also needs to cancel the operation, combine signals:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchWithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callerSignal&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;signal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;AbortSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nx"&gt;callerSignal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;AbortSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;signal&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;A timeout does not tell you whether the server completed an operation before the client stopped waiting. That distinction becomes critical for requests with side effects.&lt;/p&gt;
&lt;h2&gt;
  
  
  Retry only failures that may be temporary
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;400 Bad Request&lt;/code&gt; usually will not improve after a pause. Neither will an authentication failure caused by an invalid token.&lt;/p&gt;

&lt;p&gt;Good retry candidates often include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;408 Request Timeout&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;429 Too Many Requests&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;selected &lt;code&gt;5xx&lt;/code&gt; responses,&lt;/li&gt;
&lt;li&gt;a temporary network failure,&lt;/li&gt;
&lt;li&gt;a per-attempt timeout when repeating the operation is safe.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Treat this list as a policy for the API you are calling, not a universal law.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RETRYABLE_STATUS_CODES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="mi"&gt;408&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;502&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;504&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isRetryableResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&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;RETRYABLE_STATUS_CODES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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;Do not retry every &lt;code&gt;5xx&lt;/code&gt; response automatically without considering the service contract. Some APIs document narrower behavior, while others provide a specific &lt;code&gt;Retry-After&lt;/code&gt; response.&lt;/p&gt;
&lt;h2&gt;
  
  
  Add exponential backoff
&lt;/h2&gt;

&lt;p&gt;Immediate retries create more load when the service is already struggling.&lt;/p&gt;

&lt;p&gt;Exponential backoff increases the delay after each failed attempt:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;backoffDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baseDelay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;)&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;baseDelay&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="nx"&gt;attempt&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;That produces delays such as:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;250 ms
500 ms
1000 ms
2000 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Backoff gives the dependency time to recover and reduces repeated pressure from one client.&lt;/p&gt;

&lt;p&gt;It does not solve synchronization across many clients.&lt;/p&gt;
&lt;h2&gt;
  
  
  Add jitter so clients do not retry together
&lt;/h2&gt;

&lt;p&gt;If thousands of clients fail at the same moment and use the same deterministic backoff, they wake up together after the same delay. The retry spike has merely moved.&lt;/p&gt;

&lt;p&gt;Jitter adds randomness:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fullJitterDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baseDelay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="nx"&gt;_000&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;maximum&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;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baseDelay&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&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;floor&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;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;maximum&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;Clients now spread attempts across a time window rather than forming another synchronized wave.&lt;/p&gt;

&lt;p&gt;A small sleep helper keeps the retry loop readable:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;milliseconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;)&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;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&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;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;milliseconds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;abort&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&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="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&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="na"&gt;once&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In production code, remove abort listeners when they are no longer needed or use a well-tested utility that already handles cleanup.&lt;/p&gt;
&lt;h2&gt;
  
  
  Respect &lt;code&gt;Retry-After&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;A server may tell the client when to try again:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Retry-After: 5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The value can be a delay in seconds or an HTTP date.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseRetryAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;seconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&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;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;seconds&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000&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;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&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;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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;When the server provides a valid instruction, prefer it over an arbitrary client delay, while still enforcing your own maximum wait and total operation budget.&lt;/p&gt;
&lt;h2&gt;
  
  
  Do not retry unsafe operations blindly
&lt;/h2&gt;

&lt;p&gt;Retrying a read is usually easier to reason about than retrying a payment, order creation, email send, or database mutation.&lt;/p&gt;

&lt;p&gt;Consider this sequence:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client sends payment request
Server charges the card
Response is lost
Client times out
Client retries
Server charges the card again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A timeout proves that the client did not receive a response. It does not prove that the server did nothing.&lt;/p&gt;

&lt;p&gt;Operations with side effects need an idempotency strategy. A common pattern is a unique key representing one logical operation:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/payments&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payment&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;The server stores the key and returns the original result when the same logical operation is submitted again.&lt;/p&gt;

&lt;p&gt;This works only when the server actually implements idempotency. A client-generated header alone does not make an endpoint safe.&lt;/p&gt;
&lt;h2&gt;
  
  
  Build a bounded retry helper
&lt;/h2&gt;

&lt;p&gt;Here is a compact implementation that combines the main ideas:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RETRYABLE_STATUS_CODES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="mi"&gt;408&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;502&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;504&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fullJitterDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baseDelay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cap&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;maximum&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;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baseDelay&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&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;floor&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;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;maximum&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseRetryAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;seconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&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;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;seconds&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000&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;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;null&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;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;milliseconds&lt;/span&gt;&lt;span class="p"&gt;)&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;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;milliseconds&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&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;fetchWithRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;timeoutMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;baseDelayMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;maxDelayMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&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="p"&gt;)&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;lastError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for &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;attempt&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="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="o"&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;try&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;response&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&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="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AbortSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;RETRYABLE_STATUS_CODES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&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;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;attempts&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="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&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;retryAfter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseRetryAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Retry-After&lt;/span&gt;&lt;span class="dl"&gt;"&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;delay&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;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;maxDelayMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;retryAfter&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nf"&gt;fullJitterDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;baseDelayMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;maxDelayMs&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="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delay&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="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;lastError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;attempts&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="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&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="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;fullJitterDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baseDelayMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxDelayMs&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="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;lastError&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Request failed&lt;/span&gt;&lt;span class="dl"&gt;"&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;This is a starting point, not a universal networking library. A production client may also need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;caller cancellation,&lt;/li&gt;
&lt;li&gt;a total time budget across all attempts,&lt;/li&gt;
&lt;li&gt;domain-specific retry rules,&lt;/li&gt;
&lt;li&gt;observability,&lt;/li&gt;
&lt;li&gt;circuit breaking,&lt;/li&gt;
&lt;li&gt;connection pooling,&lt;/li&gt;
&lt;li&gt;rate-limit coordination,&lt;/li&gt;
&lt;li&gt;idempotency keys,&lt;/li&gt;
&lt;li&gt;body replay rules.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Measure retries as part of the system
&lt;/h2&gt;

&lt;p&gt;A retry that eventually succeeds can hide a degrading dependency.&lt;/p&gt;

&lt;p&gt;Track at least:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;requests by final outcome,&lt;/li&gt;
&lt;li&gt;attempts per logical operation,&lt;/li&gt;
&lt;li&gt;time spent waiting between attempts,&lt;/li&gt;
&lt;li&gt;status codes that triggered retries,&lt;/li&gt;
&lt;li&gt;requests that exhausted the retry budget,&lt;/li&gt;
&lt;li&gt;operations completed after one or more retries,&lt;/li&gt;
&lt;li&gt;duplicate-operation prevention,&lt;/li&gt;
&lt;li&gt;total latency including backoff.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A dashboard showing only successful final responses may report a healthy service while clients quietly perform two or three times the expected traffic.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  A practical retry checklist
  &lt;ul&gt;
&lt;li&gt;[ ] Every attempt has a timeout.&lt;/li&gt;
&lt;li&gt;[ ] The whole operation has a bounded time budget.&lt;/li&gt;
&lt;li&gt;[ ] Only temporary failures are retried.&lt;/li&gt;
&lt;li&gt;[ ] Backoff increases between attempts.&lt;/li&gt;
&lt;li&gt;[ ] Jitter prevents synchronized retry waves.&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;Retry-After&lt;/code&gt; is respected when appropriate.&lt;/li&gt;
&lt;li&gt;[ ] Attempts are strictly limited.&lt;/li&gt;
&lt;li&gt;[ ] Side-effecting operations are idempotent before retrying.&lt;/li&gt;
&lt;li&gt;[ ] Retry metrics are visible separately from final success rates.&lt;/li&gt;
&lt;li&gt;[ ] The client can stop retrying when the caller cancels.&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Retries are useful because distributed systems fail temporarily.&lt;/p&gt;

&lt;p&gt;Retries are dangerous for exactly the same reason: a struggling dependency has less capacity available, not more.&lt;/p&gt;

&lt;p&gt;A safer sequence is:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Set a timeout
      ↓
Classify the failure
      ↓
Check whether repeating is safe
      ↓
Wait with backoff and jitter
      ↓
Respect the retry budget
      ↓
Measure the result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Do not ask only whether the request eventually succeeded.&lt;/p&gt;

&lt;p&gt;Ask how many attempts it took, how much extra load it created, and whether repeating the operation could have caused a second side effect.&lt;/p&gt;

&lt;p&gt;What is the hardest retry failure you have diagnosed: synchronized clients, duplicate writes, ignored rate limits, or something else?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/retry-backoff.html" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Read the AWS retry with backoff guidance&lt;/a&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Sources and further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS Prescriptive Guidance:&lt;/strong&gt; &lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/retry-backoff.html" rel="noopener noreferrer"&gt;Retry with backoff pattern&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Builder Center:&lt;/strong&gt; &lt;a href="https://builder.aws.com/content/3EumjoZascWd1oZiEgL8ORlv3qE" rel="noopener noreferrer"&gt;Timeouts, retries, and backoff with jitter&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MDN:&lt;/strong&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static" rel="noopener noreferrer"&gt;AbortSignal.timeout()&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MDN:&lt;/strong&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal" rel="noopener noreferrer"&gt;AbortSignal&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&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;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%2F96a5e915-2968-4aeb-8d85-d9206cb3f703.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>programming</category>
      <category>backend</category>
      <category>webdev</category>
      <category>performance</category>
    </item>
    <item>
      <title>TypeScript 7 Is Up to 10x Faster. Should You Upgrade Now?</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Thu, 01 Oct 2026 15:30:00 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/typescript-7-is-up-to-10x-faster-should-you-upgrade-now-4d3p</link>
      <guid>https://dev.to/johnnylemonny/typescript-7-is-up-to-10x-faster-should-you-upgrade-now-4d3p</guid>
      <description>&lt;p&gt;TypeScript 7 makes an unusually attractive promise:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The same kind of type checking, much faster.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The compiler and language service have been ported to Go. Microsoft reports typical full-build improvements between 8x and 12x in large codebases, along with lower aggregate memory use in its published benchmarks.&lt;/p&gt;

&lt;p&gt;That sounds like an automatic upgrade.&lt;/p&gt;

&lt;p&gt;It is not.&lt;/p&gt;

&lt;p&gt;A faster compiler is useful only if your framework build, declaration output, editor, project references, linting, and surrounding tools still behave correctly.&lt;/p&gt;

&lt;p&gt;Fortunately, you do not need a week-long migration project to find out.&lt;/p&gt;

&lt;p&gt;You need a temporary branch, your existing verification commands, and two comparable timings.&lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  

&lt;p&gt;&lt;strong&gt;The goal of this test is not to reproduce Microsoft's benchmark.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The goal is to learn whether TypeScript 7 improves &lt;em&gt;your&lt;/em&gt; project without breaking the workflow around it.&lt;/p&gt;


&lt;/div&gt;


&lt;h2&gt;
  
  
  Why TypeScript 7 is different
&lt;/h2&gt;

&lt;p&gt;This is not a routine compiler release.&lt;/p&gt;

&lt;p&gt;TypeScript 7 replaces the previous JavaScript-based compiler and language service with a native implementation written in Go. The new implementation uses native code, shared-memory multithreading, and other optimizations intended to preserve familiar TypeScript behavior while substantially reducing wait times.&lt;/p&gt;

&lt;p&gt;Microsoft's published full-build results include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VS Code: 125.7 seconds to 10.6 seconds&lt;/li&gt;
&lt;li&gt;Sentry: 139.8 seconds to 15.7 seconds&lt;/li&gt;
&lt;li&gt;Playwright: 12.8 seconds to 1.47 seconds&lt;/li&gt;
&lt;li&gt;tldraw: 11.2 seconds to 1.46 seconds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are impressive results, but they are not a promise about every repository. A small application that spends most of its build time bundling assets or running tests may see a less dramatic end-to-end improvement.&lt;/p&gt;

&lt;p&gt;The useful question is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is TypeScript 7 fast?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How much of my development time is actually controlled by TypeScript?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Benchmark your current project
&lt;/h2&gt;

&lt;p&gt;Start from a clean working tree and record the current compiler version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx tsc &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then time the command your project already uses for type checking.&lt;/p&gt;

&lt;p&gt;On macOS or Linux:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;time &lt;/span&gt;npx tsc &lt;span class="nt"&gt;--noEmit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For a project using references:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;time &lt;/span&gt;npx tsc &lt;span class="nt"&gt;--build&lt;/span&gt; &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Also run the commands that represent the complete project workflow:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run lint
npm &lt;span class="nb"&gt;test
&lt;/span&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Do not compare a cold first run with a warm second run and call the difference a compiler improvement. Run each measurement several times under similar conditions.&lt;/p&gt;

&lt;p&gt;A small note is enough:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Current TypeScript version:
Cold type check:
Second type check:
Production build:
Tests:
Declaration build:
Editor project load:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The editor measurement can be approximate. Open the same workspace and observe how long it takes before diagnostics, references, and completions become useful.&lt;/p&gt;
&lt;h2&gt;
  
  
  Test the upgrade on a branch
&lt;/h2&gt;

&lt;p&gt;Create an isolated branch:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git switch &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt;/typescript-7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Install TypeScript 7:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; typescript@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Verify that the local executable changed:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx tsc &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now repeat the same commands you recorded earlier:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;time &lt;/span&gt;npx tsc &lt;span class="nt"&gt;--noEmit&lt;/span&gt;
npm run lint
npm &lt;span class="nb"&gt;test
&lt;/span&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If the repository uses project references, declaration generation, API extraction, code generation, or framework-specific builds, run those too.&lt;/p&gt;

&lt;p&gt;A successful &lt;code&gt;tsc --noEmit&lt;/code&gt; result is necessary, but it is not the whole migration test.&lt;/p&gt;
&lt;h2&gt;
  
  
  Compare more than type-check time
&lt;/h2&gt;

&lt;p&gt;Record the results without polishing them into a marketing claim.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;| Check | Before | TypeScript 7 | Result |
|---|---:|---:|---|
| Cold type check | your result | your result | compare |
| Second type check | your result | your result | compare |
| Production build | your result | your result | compare |
| Tests | passed/failed | passed/failed | compare |
| Declarations | passed/failed | passed/failed | compare |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The most useful observations may not fit in a timing table:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did the editor load the project faster?&lt;/li&gt;
&lt;li&gt;Did auto-imports behave normally?&lt;/li&gt;
&lt;li&gt;Did find-all-references return complete results?&lt;/li&gt;
&lt;li&gt;Did declaration files change unexpectedly?&lt;/li&gt;
&lt;li&gt;Did the framework build use the new compiler?&lt;/li&gt;
&lt;li&gt;Did linting or another tool report an incompatible peer dependency?&lt;/li&gt;
&lt;li&gt;Did CI use the same TypeScript version as the local test?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For many developers, faster diagnostics in the editor will matter more than shaving another second from an already short production build.&lt;/p&gt;
&lt;h2&gt;
  
  
  Know the main compatibility boundary
&lt;/h2&gt;

&lt;p&gt;TypeScript 7.0 does not ship a stable programmatic compiler API.&lt;/p&gt;

&lt;p&gt;That matters when a tool imports TypeScript as a library instead of simply invoking &lt;code&gt;tsc&lt;/code&gt;. Custom transformers, compiler plugins, embedded-language tooling, declaration tools, and some framework integrations may still depend on the TypeScript 6 API.&lt;/p&gt;

&lt;p&gt;Microsoft provides the &lt;code&gt;@typescript/typescript6&lt;/code&gt; compatibility package for tools that still need programmatic access while TypeScript 7 handles compilation.&lt;/p&gt;

&lt;p&gt;A side-by-side setup can look like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@typescript/native"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm:typescript@^7.0.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"typescript"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm:@typescript/typescript6@^6.0.2"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In this arrangement, the TypeScript 6 compatibility package remains available to tools expecting the established API, while the aliased native package supplies the TypeScript 7 compiler.&lt;/p&gt;

&lt;p&gt;Do not add this workaround automatically. First identify whether any tool in your project actually needs it, then follow that tool's current migration guidance.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  Projects that should test especially carefully
  &lt;p&gt;Take extra care if your project uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;custom TypeScript transformers,&lt;/li&gt;
&lt;li&gt;tools that import the compiler API,&lt;/li&gt;
&lt;li&gt;framework-specific template type checking,&lt;/li&gt;
&lt;li&gt;MDX or embedded languages,&lt;/li&gt;
&lt;li&gt;declaration bundlers,&lt;/li&gt;
&lt;li&gt;compiler-based code generation,&lt;/li&gt;
&lt;li&gt;complex project references,&lt;/li&gt;
&lt;li&gt;editor plugins tied to the older language service,&lt;/li&gt;
&lt;li&gt;build systems that pin a particular TypeScript range.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This does not mean the project cannot upgrade. It means &lt;code&gt;npx tsc --noEmit&lt;/code&gt; is not enough evidence by itself.&lt;/p&gt;



&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Check the editor separately
&lt;/h2&gt;

&lt;p&gt;TypeScript 7 uses the Language Server Protocol, which makes the new language service available beyond one editor. Microsoft also provides dedicated TypeScript 7 support for VS Code, while other editors may have their own enablement instructions.&lt;/p&gt;

&lt;p&gt;Verify the editor rather than assuming the workspace dependency controls everything.&lt;/p&gt;

&lt;p&gt;Test a few ordinary actions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open a large file with a known type error.&lt;/li&gt;
&lt;li&gt;Trigger auto-completion.&lt;/li&gt;
&lt;li&gt;Find all references for a widely used symbol.&lt;/li&gt;
&lt;li&gt;Rename a symbol across several files.&lt;/li&gt;
&lt;li&gt;Add an import using a quick fix.&lt;/li&gt;
&lt;li&gt;Restart the editor and observe project-load time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The best upgrade result is not merely a faster CI number. It is a shorter feedback loop throughout the day.&lt;/p&gt;
&lt;h2&gt;
  
  
  Keep it or roll it back
&lt;/h2&gt;

&lt;p&gt;Keep the upgrade when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the same checks pass,&lt;/li&gt;
&lt;li&gt;declaration output remains acceptable,&lt;/li&gt;
&lt;li&gt;the framework build behaves correctly,&lt;/li&gt;
&lt;li&gt;editor features work as expected,&lt;/li&gt;
&lt;li&gt;CI uses the intended compiler,&lt;/li&gt;
&lt;li&gt;the measured improvement matters to the project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Roll it back for now when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a critical tool needs the older compiler API,&lt;/li&gt;
&lt;li&gt;generated output changes unexpectedly,&lt;/li&gt;
&lt;li&gt;editor support is incomplete for your setup,&lt;/li&gt;
&lt;li&gt;the framework has not yet documented support,&lt;/li&gt;
&lt;li&gt;the performance gain does not justify the migration work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rolling back the experiment is simple:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git switch -
git branch &lt;span class="nt"&gt;-D&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt;/typescript-7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Alternatively, keep the branch and open an issue listing the blockers. A measured “not yet” is a useful result.&lt;/p&gt;
&lt;h2&gt;
  
  
  A ten-minute upgrade checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Record the current TypeScript version.&lt;/li&gt;
&lt;li&gt;[ ] Time the existing type-check command more than once.&lt;/li&gt;
&lt;li&gt;[ ] Run tests, linting, and the production build.&lt;/li&gt;
&lt;li&gt;[ ] Create a separate upgrade branch.&lt;/li&gt;
&lt;li&gt;[ ] Install TypeScript 7 and verify the active executable.&lt;/li&gt;
&lt;li&gt;[ ] Repeat the same measurements.&lt;/li&gt;
&lt;li&gt;[ ] Compare editor behavior.&lt;/li&gt;
&lt;li&gt;[ ] Check declaration output and project references.&lt;/li&gt;
&lt;li&gt;[ ] Identify tools that import TypeScript programmatically.&lt;/li&gt;
&lt;li&gt;[ ] Confirm that CI uses the intended version.&lt;/li&gt;
&lt;li&gt;[ ] Keep the change only when correctness and tooling still hold.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;TypeScript 7 is exciting because the improvement is not a small optimization around the edges. It replaces the compiler and language service with a native implementation designed for speed and parallelism.&lt;/p&gt;

&lt;p&gt;But a headline benchmark is not a migration plan.&lt;/p&gt;

&lt;p&gt;The safest approach is short and practical:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Measure the current project
          ↓
Create a temporary branch
          ↓
Install TypeScript 7
          ↓
Run the same checks
          ↓
Verify editor and tooling behavior
          ↓
Keep the upgrade or document the blocker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you test TypeScript 7, share four details in the comments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;approximate project size,&lt;/li&gt;
&lt;li&gt;previous TypeScript version,&lt;/li&gt;
&lt;li&gt;old type-check time,&lt;/li&gt;
&lt;li&gt;new type-check time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Small applications and large monorepos may tell very different stories.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Read the official TypeScript 7 announcement&lt;/a&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Sources and further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Microsoft TypeScript Blog:&lt;/strong&gt; &lt;a href="https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/" rel="noopener noreferrer"&gt;Announcing TypeScript 7.0&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microsoft TypeScript Blog:&lt;/strong&gt; &lt;a href="https://devblogs.microsoft.com/typescript/progress-on-typescript-7-december-2025/" rel="noopener noreferrer"&gt;Progress on TypeScript 7&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&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;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%2F96a5e915-2968-4aeb-8d85-d9206cb3f703.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>typescript</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Stop Waiting for GitHub Actions: Make Your CI Faster Today</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Tue, 29 Sep 2026 15:30:00 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/stop-waiting-for-github-actions-make-your-ci-faster-today-a2l</link>
      <guid>https://dev.to/johnnylemonny/stop-waiting-for-github-actions-make-your-ci-faster-today-a2l</guid>
      <description>&lt;p&gt;You push a small README correction.&lt;/p&gt;

&lt;p&gt;GitHub Actions starts from a clean runner, downloads every dependency, runs the linter, checks every type, executes the complete test suite, builds the application, and uploads an artifact nobody will download.&lt;/p&gt;

&lt;p&gt;Before it finishes, you notice another typo and push again.&lt;/p&gt;

&lt;p&gt;Now both workflows are running.&lt;/p&gt;

&lt;p&gt;Nothing is technically broken. The workflow is simply doing more work than the change requires.&lt;/p&gt;

&lt;p&gt;A slow CI pipeline is not only an infrastructure problem. It delays code review, interrupts concentration, consumes runner time, and makes every small pull request feel heavier than it should.&lt;/p&gt;

&lt;p&gt;The good news is that the first improvements are usually small.&lt;/p&gt;

&lt;p&gt;This guide starts with a deliberately ordinary Node.js workflow and improves it step by step. The same principles apply to other ecosystems, even when the exact setup and cache commands differ.&lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  

&lt;p&gt;&lt;strong&gt;The goal is not the shortest possible workflow.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The goal is fast, trustworthy feedback with no unnecessary work.&lt;/p&gt;


&lt;/div&gt;


&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Start with evidence, not guesses&lt;/li&gt;
&lt;li&gt;The workflow we are fixing&lt;/li&gt;
&lt;li&gt;Fix 1: cancel outdated runs&lt;/li&gt;
&lt;li&gt;Fix 2: cache dependency downloads&lt;/li&gt;
&lt;li&gt;Fix 3: use deterministic installs&lt;/li&gt;
&lt;li&gt;Fix 4: put cheap failures first&lt;/li&gt;
&lt;li&gt;Fix 5: parallelize only independent work&lt;/li&gt;
&lt;li&gt;Fix 6: avoid irrelevant runs&lt;/li&gt;
&lt;li&gt;Fix 7: stop uploading unused artifacts&lt;/li&gt;
&lt;li&gt;Add limits and minimum permissions&lt;/li&gt;
&lt;li&gt;Treat caches as untrusted input&lt;/li&gt;
&lt;li&gt;The complete improved workflow&lt;/li&gt;
&lt;li&gt;A practical optimization checklist&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a id="start-with-evidence"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with evidence, not guesses
&lt;/h2&gt;

&lt;p&gt;Do not optimize a workflow because one step &lt;em&gt;looks&lt;/em&gt; slow.&lt;/p&gt;

&lt;p&gt;Open a representative run in the &lt;strong&gt;Actions&lt;/strong&gt; tab and record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;total workflow duration,&lt;/li&gt;
&lt;li&gt;queue time,&lt;/li&gt;
&lt;li&gt;duration of each job,&lt;/li&gt;
&lt;li&gt;duration of dependency installation,&lt;/li&gt;
&lt;li&gt;duration of linting, type checking, tests, and builds,&lt;/li&gt;
&lt;li&gt;how often runs are canceled or superseded,&lt;/li&gt;
&lt;li&gt;cache hit and miss behavior,&lt;/li&gt;
&lt;li&gt;artifact size and whether anyone downloads it,&lt;/li&gt;
&lt;li&gt;repeated work across jobs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use several runs rather than one unusual result. A temporary package registry delay or cold runner can distort a single measurement.&lt;/p&gt;

&lt;p&gt;A simple baseline is enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dependency installation:  52 seconds
Lint and type check:       24 seconds
Tests:                    104 seconds
Build:                     47 seconds
Artifact upload:           18 seconds
Total workflow:           245 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Your numbers will be different. The important part is knowing where the time goes before changing the YAML.&lt;/p&gt;
&lt;h3&gt;
  
  
  Optimize feedback time as well as total time
&lt;/h3&gt;

&lt;p&gt;Two workflows can have the same total duration but feel very different.&lt;/p&gt;

&lt;p&gt;If a formatting error is reported after four minutes of testing, the developer waits four minutes for information that could have arrived in twenty seconds. Moving fast checks earlier may improve the development loop even if a successful run is not dramatically shorter.&lt;/p&gt;

&lt;p&gt;Useful measurements include:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Time to first useful failure
Time to all required checks passing
Runner minutes consumed per pull request
Percentage of runs canceled as outdated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="crayons-card c-embed"&gt;

  

&lt;p&gt;A fast green run is useful. A fast, precise failure is often more useful.&lt;/p&gt;


&lt;/div&gt;



&lt;p&gt;&lt;a id="starting-workflow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow we are fixing
&lt;/h2&gt;

&lt;p&gt;Here is a common first version:&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;CI&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;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&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;ci&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check out repository&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@v5&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;Set up Node.js&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@v6&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;24&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;Install dependencies&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 install&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;Lint&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 lint&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;Type check&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 typecheck&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;Test&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 test&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;Build&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="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;Upload build&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/upload-artifact@v5&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application-build&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;dist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This workflow is understandable, which is a good start. It also has several opportunities for improvement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every push starts another run,&lt;/li&gt;
&lt;li&gt;dependency downloads are not cached,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm install&lt;/code&gt; may change the resolved dependency tree,&lt;/li&gt;
&lt;li&gt;every check executes in one long sequence,&lt;/li&gt;
&lt;li&gt;documentation-only changes run the full pipeline,&lt;/li&gt;
&lt;li&gt;every successful run uploads a build,&lt;/li&gt;
&lt;li&gt;there is no timeout,&lt;/li&gt;
&lt;li&gt;permissions are implicit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will improve these one at a time.&lt;/p&gt;

&lt;p&gt;&lt;a id="cancel-outdated-runs"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Fix 1: cancel outdated runs
&lt;/h2&gt;

&lt;p&gt;Suppose a pull request receives three commits in ten minutes. In most CI workflows, only the newest commit matters. Finishing tests for the first two revisions provides little value once a newer revision exists.&lt;/p&gt;

&lt;p&gt;Add workflow-level concurrency:&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;concurrency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ci-${{ github.workflow }}-${{ github.ref }}&lt;/span&gt;
  &lt;span class="na"&gt;cancel-in-progress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Full context:&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;CI&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;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ci-${{ github.workflow }}-${{ github.ref }}&lt;/span&gt;
  &lt;span class="na"&gt;cancel-in-progress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Runs sharing the same concurrency group will not continue in parallel. With &lt;code&gt;cancel-in-progress: true&lt;/code&gt;, a new run cancels the older active run in that group.&lt;/p&gt;

&lt;p&gt;Including &lt;code&gt;github.workflow&lt;/code&gt; prevents unrelated workflows from accidentally sharing the same group. Including &lt;code&gt;github.ref&lt;/code&gt; keeps different branches or pull requests separate.&lt;/p&gt;
&lt;h3&gt;
  
  
  When not to cancel
&lt;/h3&gt;

&lt;p&gt;Cancellation is a good fit for validation of changing pull requests. It may be wrong for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deployments that must complete in order,&lt;/li&gt;
&lt;li&gt;database migrations,&lt;/li&gt;
&lt;li&gt;release publishing,&lt;/li&gt;
&lt;li&gt;stateful integration processes,&lt;/li&gt;
&lt;li&gt;jobs that perform irreversible external actions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For those cases, use a separate concurrency group and consider queueing rather than cancellation.&lt;/p&gt;

&lt;p&gt;Do not apply one concurrency policy to every workflow without considering what interruption means.&lt;/p&gt;

&lt;p&gt;&lt;a id="cache-dependencies"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Fix 2: cache dependency downloads
&lt;/h2&gt;

&lt;p&gt;GitHub-hosted jobs start on clean runner images. Without caching, package managers repeatedly download the same dependency archives.&lt;/p&gt;

&lt;p&gt;For npm, &lt;code&gt;setup-node&lt;/code&gt; can manage the package-manager cache:&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="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;Set up Node.js&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@v6&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;24&lt;/span&gt;
    &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&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;Install dependencies&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This caches npm's download cache, not the completed &lt;code&gt;node_modules&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm ci&lt;/code&gt; still reconstructs the dependency installation from the lockfile. It simply has a better chance of retrieving packages from a restored local cache instead of downloading every archive again.&lt;/p&gt;
&lt;h3&gt;
  
  
  Cache the package manager, not &lt;code&gt;node_modules&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Caching &lt;code&gt;node_modules&lt;/code&gt; can look faster in a quick experiment, but it couples the cached directory to details such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;operating system,&lt;/li&gt;
&lt;li&gt;architecture,&lt;/li&gt;
&lt;li&gt;Node.js version,&lt;/li&gt;
&lt;li&gt;native module compilation,&lt;/li&gt;
&lt;li&gt;package-manager behavior,&lt;/li&gt;
&lt;li&gt;lifecycle scripts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A package-manager cache is usually more portable and easier to reason about. The workflow should also remain correct on a cache miss.&lt;/p&gt;
&lt;h3&gt;
  
  
  Monorepos need the correct lockfile
&lt;/h3&gt;

&lt;p&gt;If the lockfile is not in the repository root, declare the dependency path:&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="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;Set up Node.js&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@v6&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;24&lt;/span&gt;
    &lt;span class="na"&gt;cache&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;cache-dependency-path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/web/package-lock.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For multiple lockfiles:&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;cache-dependency-path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
  &lt;span class="s"&gt;apps/web/package-lock.json&lt;/span&gt;
  &lt;span class="s"&gt;apps/api/package-lock.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A cache is only useful when its key changes with the dependency definition that matters.&lt;/p&gt;
&lt;h3&gt;
  
  
  Verify that the cache actually helps
&lt;/h3&gt;

&lt;p&gt;After enabling caching, compare several runs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the first run should usually miss and populate the cache,&lt;/li&gt;
&lt;li&gt;later runs with the same lockfile should restore it,&lt;/li&gt;
&lt;li&gt;a changed lockfile should create a new applicable cache entry,&lt;/li&gt;
&lt;li&gt;dependency installation must still succeed when no cache exists.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If install time barely changes, the bottleneck may be lifecycle scripts, native compilation, network access outside the package manager, or work performed after download.&lt;/p&gt;

&lt;p&gt;&lt;a id="deterministic-installs"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Fix 3: use deterministic installs
&lt;/h2&gt;

&lt;p&gt;In CI, prefer:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm ci
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;over:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;npm ci&lt;/code&gt; expects a lockfile, removes an existing &lt;code&gt;node_modules&lt;/code&gt; directory, and installs the dependency tree represented by the lockfile. It also fails when &lt;code&gt;package.json&lt;/code&gt; and the lockfile disagree instead of silently updating the lockfile.&lt;/p&gt;

&lt;p&gt;That gives CI a clearer job:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Verify the dependency state committed to the repository.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It also avoids a workflow that succeeds with one dependency resolution while developers or production use another.&lt;/p&gt;

&lt;p&gt;Equivalent deterministic commands exist in other package managers. Use the command recommended for reproducible CI installs in your ecosystem.&lt;/p&gt;
&lt;h3&gt;
  
  
  Keep runtime versions explicit
&lt;/h3&gt;

&lt;p&gt;Do not rely on whatever runtime happens to be preinstalled on the current runner image.&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="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@v6&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;24&lt;/span&gt;
    &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If the project supports several active Node.js versions, test them intentionally with a matrix rather than accepting accidental variation.&lt;/p&gt;

&lt;p&gt;&lt;a id="cheap-failures-first"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Fix 4: put cheap failures first
&lt;/h2&gt;

&lt;p&gt;A workflow should report obvious problems before starting expensive work.&lt;/p&gt;

&lt;p&gt;A reasonable order for many projects is:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Install
  ↓
Formatting or linting
  ↓
Type checking
  ↓
Unit tests
  ↓
Integration tests
  ↓
Production build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That does not mean every step must be sequential. It means the workflow should consider cost and diagnostic value.&lt;/p&gt;
&lt;h3&gt;
  
  
  Sequential approach
&lt;/h3&gt;

&lt;p&gt;One job is simple and reuses one dependency installation:&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;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@v5&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@v6&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;24&lt;/span&gt;
      &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&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 lint&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 typecheck&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 test&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;Advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one checkout,&lt;/li&gt;
&lt;li&gt;one runtime setup,&lt;/li&gt;
&lt;li&gt;one dependency installation,&lt;/li&gt;
&lt;li&gt;easy logs,&lt;/li&gt;
&lt;li&gt;later work stops after an earlier failure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Disadvantage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;independent checks cannot run in parallel.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For small repositories, this may be the fastest and easiest design.&lt;/p&gt;
&lt;h3&gt;
  
  
  Gated jobs
&lt;/h3&gt;

&lt;p&gt;If tests are expensive and linting commonly fails, use a cheap quality gate:&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;quality&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;timeout-minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&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@v5&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@v6&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;24&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&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 lint&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 typecheck&lt;/span&gt;

  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;quality&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;timeout-minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&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@v5&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@v6&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;24&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&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 test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This avoids running tests after a quality failure, but each job checks out the repository and installs dependencies separately.&lt;/p&gt;

&lt;p&gt;The trade-off is real. Measure it.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  How should I choose between one job and several jobs?
  &lt;p&gt;Prefer one job when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the repository is small,&lt;/li&gt;
&lt;li&gt;dependency installation is significant,&lt;/li&gt;
&lt;li&gt;checks are short,&lt;/li&gt;
&lt;li&gt;a simple log is valuable,&lt;/li&gt;
&lt;li&gt;later steps should stop after the first failure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider several jobs when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tasks are long and independent,&lt;/li&gt;
&lt;li&gt;parallel work meaningfully reduces feedback time,&lt;/li&gt;
&lt;li&gt;different jobs need different runners or permissions,&lt;/li&gt;
&lt;li&gt;branch protection should show distinct required checks,&lt;/li&gt;
&lt;li&gt;one cheap gate can prevent substantial expensive work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not split jobs only because a longer YAML file looks more sophisticated.&lt;/p&gt;



&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;a id="parallelize-carefully"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Fix 5: parallelize only independent work
&lt;/h2&gt;

&lt;p&gt;Parallel jobs can reduce wall-clock time, but they can also increase total runner usage.&lt;/p&gt;

&lt;p&gt;Suppose linting takes one minute, tests take four minutes, and the build takes three minutes.&lt;/p&gt;

&lt;p&gt;Sequential execution takes roughly eight minutes after setup. Running all three in parallel may finish in about four minutes, but each job repeats setup and dependency installation.&lt;/p&gt;

&lt;p&gt;That can be a good trade when developer feedback is the priority. It can be wasteful when jobs are short or runner usage is constrained.&lt;/p&gt;
&lt;h3&gt;
  
  
  A balanced structure
&lt;/h3&gt;

&lt;p&gt;Use one lightweight required job and parallelize the expensive independent work after it:&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;quality&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@v5&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@v6&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;24&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&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 lint&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 typecheck&lt;/span&gt;

  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;quality&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@v5&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@v6&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;24&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&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 test&lt;/span&gt;

  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;quality&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@v5&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@v6&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;24&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&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;After &lt;code&gt;quality&lt;/code&gt; succeeds, &lt;code&gt;test&lt;/code&gt; and &lt;code&gt;build&lt;/code&gt; can run at the same time.&lt;/p&gt;
&lt;h3&gt;
  
  
  Avoid unnecessary matrices
&lt;/h3&gt;

&lt;p&gt;A matrix is valuable when the project genuinely supports several runtimes or platforms:&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;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;matrix&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="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;22&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;24&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Every matrix combination creates another job. Do not test combinations your project does not claim to support.&lt;/p&gt;

&lt;p&gt;For pull requests, it may be reasonable to test the primary runtime and reserve the full compatibility matrix for pushes to &lt;code&gt;main&lt;/code&gt; or scheduled runs. Make that choice explicit and ensure changes cannot merge without the coverage your project actually requires.&lt;/p&gt;

&lt;p&gt;&lt;a id="avoid-irrelevant-runs"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Fix 6: avoid irrelevant runs
&lt;/h2&gt;

&lt;p&gt;If a workflow validates only an application under &lt;code&gt;src/&lt;/code&gt;, a change to unrelated documentation may not require the full test and build pipeline.&lt;/p&gt;

&lt;p&gt;Path filters can narrow the trigger:&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;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;src/**"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tests/**"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;package.json"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;package-lock.json"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tsconfig.json"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.github/workflows/ci.yml"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For a monorepo, a service-specific workflow might use:&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;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apps/api/**"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;packages/shared/**"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.github/workflows/api-ci.yml"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Path filters can create merge problems
&lt;/h3&gt;

&lt;p&gt;GitHub warns that when a required workflow is skipped because of path or branch filtering, its associated check can remain pending and block the pull request.&lt;/p&gt;

&lt;p&gt;Before adding filters, review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;branch protection rules,&lt;/li&gt;
&lt;li&gt;required check names,&lt;/li&gt;
&lt;li&gt;shared packages that affect several applications,&lt;/li&gt;
&lt;li&gt;generated files,&lt;/li&gt;
&lt;li&gt;root configuration,&lt;/li&gt;
&lt;li&gt;workflow files themselves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For complex monorepos, a small always-running classification job may be safer than completely skipping a required workflow. That job can determine which test jobs need to run while still producing a stable required check.&lt;/p&gt;
&lt;h3&gt;
  
  
  Be cautious with skip commands
&lt;/h3&gt;

&lt;p&gt;Commit messages such as &lt;code&gt;[skip ci]&lt;/code&gt; can suppress workflows triggered by &lt;code&gt;push&lt;/code&gt; or &lt;code&gt;pull_request&lt;/code&gt;, but skipped required checks may remain pending. A manual skip should not become the normal optimization strategy.&lt;/p&gt;

&lt;p&gt;Use trigger design and job conditions that are understandable to the whole team.&lt;/p&gt;

&lt;p&gt;&lt;a id="artifacts"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Fix 7: stop uploading unused artifacts
&lt;/h2&gt;

&lt;p&gt;Artifacts are useful for files that must survive after a job finishes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;packaged applications,&lt;/li&gt;
&lt;li&gt;test reports,&lt;/li&gt;
&lt;li&gt;screenshots from failed browser tests,&lt;/li&gt;
&lt;li&gt;coverage reports,&lt;/li&gt;
&lt;li&gt;debugging logs,&lt;/li&gt;
&lt;li&gt;release candidates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are not free decoration for every successful run.&lt;/p&gt;

&lt;p&gt;If nobody downloads the build from ordinary pull requests, do not upload it on every pull request.&lt;/p&gt;

&lt;p&gt;Upload only on &lt;code&gt;main&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="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;Upload build&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name == 'push' &amp;amp;&amp;amp; github.ref == 'refs/heads/main'&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/upload-artifact@v5&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application-build&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;dist&lt;/span&gt;
    &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Or upload diagnostic material only after failure:&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="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;Upload test diagnostics&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;failure()&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/upload-artifact@v5&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-diagnostics&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;test-results&lt;/span&gt;
    &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Cache and artifacts solve different problems
&lt;/h3&gt;

&lt;p&gt;Use a &lt;strong&gt;cache&lt;/strong&gt; for reusable data that can be regenerated, such as downloaded dependencies.&lt;/p&gt;

&lt;p&gt;Use an &lt;strong&gt;artifact&lt;/strong&gt; for output that someone or another job needs to inspect or consume.&lt;/p&gt;

&lt;p&gt;A cache miss should make the workflow slower, not incorrect. Losing a required release artifact may be a real failure.&lt;/p&gt;

&lt;p&gt;Choose retention deliberately. Keeping every temporary report for the maximum period creates storage without necessarily creating value.&lt;/p&gt;

&lt;p&gt;&lt;a id="limits-and-permissions"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Add limits and minimum permissions
&lt;/h2&gt;

&lt;p&gt;A hung command should not consume runner time indefinitely.&lt;/p&gt;

&lt;p&gt;Set a timeout for every job:&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;quality&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;timeout-minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Use a value based on observed normal duration, with enough room for ordinary variance. A job that usually finishes in two minutes probably does not need a six-hour ceiling.&lt;/p&gt;
&lt;h3&gt;
  
  
  Make permissions explicit
&lt;/h3&gt;

&lt;p&gt;A CI workflow that only reads repository content can often start with:&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;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;read&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Place this at workflow level, then increase permissions only for the specific job that needs them.&lt;/p&gt;

&lt;p&gt;Minimal permissions improve security more than speed, but they also make the workflow easier to audit. Optimization should not trade away safety for a few seconds.&lt;/p&gt;
&lt;h3&gt;
  
  
  Pin actions according to your security policy
&lt;/h3&gt;

&lt;p&gt;Version tags are easy to read:&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;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Security-sensitive organizations may require actions to be pinned to full commit SHAs. GitHub provides repository and organization policies for restricting which actions can run and requiring full-length SHA pinning.&lt;/p&gt;

&lt;p&gt;Follow the policy appropriate to your project, and use automated dependency updates so action references do not become stale.&lt;/p&gt;

&lt;p&gt;&lt;a id="cache-security"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Treat caches as untrusted input
&lt;/h2&gt;

&lt;p&gt;Caching is an optimization boundary, not a secret store.&lt;/p&gt;

&lt;p&gt;Never place these in a cache path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API keys,&lt;/li&gt;
&lt;li&gt;access tokens,&lt;/li&gt;
&lt;li&gt;environment files containing secrets,&lt;/li&gt;
&lt;li&gt;signing keys,&lt;/li&gt;
&lt;li&gt;production credentials,&lt;/li&gt;
&lt;li&gt;unprotected deployment configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub warns that workflows able to read a cache restore its contents as-is. Fork-based pull requests and low-trust workflow events make cache permissions especially important, and poisoned caches can affect later trusted work.&lt;/p&gt;

&lt;p&gt;In September 2026, GitHub made &lt;code&gt;cache-mode&lt;/code&gt; generally available across plans. It lets workflows or jobs declare cache access as:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;read       restore but do not save
write      restore and save
write-only save but do not restore
none       disable cache access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The secure default depends on the event. Low-trust events receive restricted behavior, and explicitly granting write access can reintroduce cache-poisoning risk.&lt;/p&gt;

&lt;p&gt;Do not add a write-capable cache mode to an untrusted event merely to avoid a cache miss. Read the current GitHub syntax documentation and grant only the access the job needs.&lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  

&lt;p&gt;A faster compromised workflow is not an optimization.&lt;/p&gt;

&lt;p&gt;Treat restored cache content as untrusted, keep secrets out of caches, and use the least cache access required by each event.&lt;/p&gt;


&lt;/div&gt;



&lt;p&gt;&lt;a id="complete-workflow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The complete improved workflow
&lt;/h2&gt;

&lt;p&gt;The best final structure depends on your repository. The following example favors clear feedback and manageable complexity for a typical Node.js project.&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;CI&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;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&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;read&lt;/span&gt;

&lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ci-${{ github.workflow }}-${{ github.ref }}&lt;/span&gt;
  &lt;span class="na"&gt;cancel-in-progress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;quality&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;Lint and type check&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;timeout-minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check out repository&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@v5&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;Set up Node.js&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@v6&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;24&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&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;Install dependencies&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lint&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 lint&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;Type check&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 typecheck&lt;/span&gt;

  &lt;span class="na"&gt;test&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;Test&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;quality&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;timeout-minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check out repository&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@v5&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;Set up Node.js&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@v6&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;24&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&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;Install dependencies&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run tests&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 test&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;Upload test diagnostics&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;failure()&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/upload-artifact@v5&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-diagnostics-${{ github.run_id }}&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;test-results&lt;/span&gt;
          &lt;span class="na"&gt;if-no-files-found&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ignore&lt;/span&gt;
          &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;

  &lt;span class="na"&gt;build&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;Build&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;quality&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;timeout-minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check out repository&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@v5&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;Set up Node.js&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@v6&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;24&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&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;Install dependencies&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&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="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;Upload main-branch build&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name == 'push' &amp;amp;&amp;amp; github.ref == 'refs/heads/main'&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/upload-artifact@v5&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application-build-${{ github.sha }}&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;dist&lt;/span&gt;
          &lt;span class="na"&gt;if-no-files-found&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;error&lt;/span&gt;
          &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;What changed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pushes to feature branches no longer duplicate pull request validation,&lt;/li&gt;
&lt;li&gt;older runs for the same ref are canceled,&lt;/li&gt;
&lt;li&gt;npm download caching is enabled,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm ci&lt;/code&gt; produces deterministic installs,&lt;/li&gt;
&lt;li&gt;cheap quality checks run before expensive jobs,&lt;/li&gt;
&lt;li&gt;tests and builds run in parallel after the quality gate,&lt;/li&gt;
&lt;li&gt;artifacts are uploaded only when useful,&lt;/li&gt;
&lt;li&gt;failed tests can preserve diagnostics,&lt;/li&gt;
&lt;li&gt;every job has a timeout,&lt;/li&gt;
&lt;li&gt;repository permissions are explicit.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Is this always faster?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;For a small project, three separate &lt;code&gt;npm ci&lt;/code&gt; executions may cost more than the parallelism saves. In that case, keep one job and retain the other improvements:&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;ci&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;timeout-minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&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@v5&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@v6&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;24&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&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 lint&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 typecheck&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 test&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;The article cannot choose for your repository. Your measurements can.&lt;/p&gt;
&lt;h2&gt;
  
  
  Changes that look fast but often backfire
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Caching everything
&lt;/h3&gt;

&lt;p&gt;A huge, unstable cache may take longer to restore than the work it replaces. It also increases invalidation and security complexity.&lt;/p&gt;
&lt;h3&gt;
  
  
  Splitting every command into its own job
&lt;/h3&gt;

&lt;p&gt;Parallel YAML is not automatically efficient. Each job has setup overhead and consumes runner time.&lt;/p&gt;
&lt;h3&gt;
  
  
  Skipping tests based on weak path rules
&lt;/h3&gt;

&lt;p&gt;A root configuration or shared package may affect more applications than the filter suggests.&lt;/p&gt;
&lt;h3&gt;
  
  
  Removing verification to save time
&lt;/h3&gt;

&lt;p&gt;A workflow that finishes quickly because it no longer checks important behavior is not improved.&lt;/p&gt;
&lt;h3&gt;
  
  
  Using broad restore keys without understanding them
&lt;/h3&gt;

&lt;p&gt;A partial cache match can restore older content. That may be acceptable for package downloads, but risky for build outputs that depend on exact source, compiler, or environment state.&lt;/p&gt;
&lt;h3&gt;
  
  
  Running the full compatibility matrix on every draft commit
&lt;/h3&gt;

&lt;p&gt;Test what is necessary for pull request feedback, then run broader compatibility checks at the stage where they provide value.&lt;/p&gt;
&lt;h3&gt;
  
  
  Uploading every output forever
&lt;/h3&gt;

&lt;p&gt;Artifacts need a consumer and a retention policy.&lt;/p&gt;

&lt;p&gt;&lt;a id="optimization-checklist"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  A practical optimization checklist
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Measure
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Record total duration across several representative runs.&lt;/li&gt;
&lt;li&gt;[ ] Identify the slowest steps and repeated setup.&lt;/li&gt;
&lt;li&gt;[ ] Measure time to first useful failure.&lt;/li&gt;
&lt;li&gt;[ ] Check queue time separately from execution time.&lt;/li&gt;
&lt;li&gt;[ ] Review runner minutes and artifact storage.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Eliminate outdated work
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Add an appropriate concurrency group.&lt;/li&gt;
&lt;li&gt;[ ] Cancel superseded pull request runs.&lt;/li&gt;
&lt;li&gt;[ ] Keep deployments and irreversible jobs on a safer policy.&lt;/li&gt;
&lt;li&gt;[ ] Avoid duplicate push and pull request runs for the same feature branch.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Improve dependency setup
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Use the deterministic install command for the package manager.&lt;/li&gt;
&lt;li&gt;[ ] Cache package-manager downloads.&lt;/li&gt;
&lt;li&gt;[ ] Include the correct lockfile in cache configuration.&lt;/li&gt;
&lt;li&gt;[ ] Verify behavior on both cache hits and misses.&lt;/li&gt;
&lt;li&gt;[ ] Keep secrets and sensitive files out of cache paths.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Improve feedback
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Put cheap, precise checks before expensive work when useful.&lt;/li&gt;
&lt;li&gt;[ ] Parallelize only independent tasks.&lt;/li&gt;
&lt;li&gt;[ ] Keep job boundaries understandable.&lt;/li&gt;
&lt;li&gt;[ ] Use the full compatibility matrix only where it adds value.&lt;/li&gt;
&lt;li&gt;[ ] Preserve useful diagnostics after failure.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Reduce irrelevant execution
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Review event and branch triggers.&lt;/li&gt;
&lt;li&gt;[ ] Use path filters only after checking required-check behavior.&lt;/li&gt;
&lt;li&gt;[ ] Include shared configuration and packages in relevant filters.&lt;/li&gt;
&lt;li&gt;[ ] Do not make manual skip messages the main workflow strategy.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Control resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Add realistic job timeouts.&lt;/li&gt;
&lt;li&gt;[ ] Upload artifacts only when someone or another job needs them.&lt;/li&gt;
&lt;li&gt;[ ] Set intentional artifact retention periods.&lt;/li&gt;
&lt;li&gt;[ ] Use explicit minimum permissions.&lt;/li&gt;
&lt;li&gt;[ ] Review action pinning and update policy.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;A faster GitHub Actions workflow usually does not require faster hardware.&lt;/p&gt;

&lt;p&gt;It requires less unnecessary work.&lt;/p&gt;

&lt;p&gt;Start with the changes that have the clearest value:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Measure the current run
          ↓
Cancel outdated work
          ↓
Cache dependency downloads
          ↓
Use deterministic installs
          ↓
Report cheap failures early
          ↓
Parallelize only where it helps
          ↓
Skip irrelevant work carefully
          ↓
Retain only useful artifacts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then measure again.&lt;/p&gt;

&lt;p&gt;Do not optimize for an impressive YAML file. Optimize for reliable feedback that arrives while the developer still remembers the change.&lt;/p&gt;

&lt;p&gt;Which step consumes the most time in your current GitHub Actions workflow?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/actions" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Explore the GitHub Actions documentation&lt;/a&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Sources and further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Docs:&lt;/strong&gt; &lt;a href="https://docs.github.com/en/actions/concepts/workflows-and-actions/concurrency" rel="noopener noreferrer"&gt;Concurrency&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Docs:&lt;/strong&gt; &lt;a href="https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency" rel="noopener noreferrer"&gt;Control the concurrency of workflows and jobs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Docs:&lt;/strong&gt; &lt;a href="https://docs.github.com/en/actions/concepts/workflows-and-actions/dependency-caching" rel="noopener noreferrer"&gt;Dependency caching&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Docs:&lt;/strong&gt; &lt;a href="https://docs.github.com/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows" rel="noopener noreferrer"&gt;Dependency caching reference&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Docs:&lt;/strong&gt; &lt;a href="https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax" rel="noopener noreferrer"&gt;Workflow syntax for GitHub Actions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Docs:&lt;/strong&gt; &lt;a href="https://docs.github.com/en/actions/how-tos/manage-workflow-runs/skip-workflow-runs" rel="noopener noreferrer"&gt;Skipping workflow runs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Docs:&lt;/strong&gt; &lt;a href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository" rel="noopener noreferrer"&gt;Managing GitHub Actions settings&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Changelog:&lt;/strong&gt; &lt;a href="https://github.blog/changelog/2026-09-10-control-github-actions-cache-access-with-cache-mode/" rel="noopener noreferrer"&gt;Control GitHub Actions cache access with cache-mode&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&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;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%2F96a5e915-2968-4aeb-8d85-d9206cb3f703.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>github</category>
      <category>devops</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Good read. I think a lot of people confuse confidence with probability, so this distinction really matters in practice.</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Mon, 28 Sep 2026 20:06:46 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/good-read-i-think-a-lot-of-people-confuse-confidence-with-probability-so-this-distinction-really-44bg</link>
      <guid>https://dev.to/johnnylemonny/good-read-i-think-a-lot-of-people-confuse-confidence-with-probability-so-this-distinction-really-44bg</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/raju_dandigam/a-confidence-score-is-not-a-probability-act-ask-or-abstain-4g3k" class="crayons-story__hidden-navigation-link"&gt;A Confidence Score Is Not a Probability: Act, Ask, or Abstain&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/raju_dandigam" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F1726463%2F38d1e46f-d122-4fa3-b130-772169c24466.png" alt="raju_dandigam profile" class="crayons-avatar__image" width="800" height="743"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/raju_dandigam" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Raju Dandigam
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Raju Dandigam
                
                
              
              &lt;div id="story-author-preview-content-4619833" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/raju_dandigam" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F1726463%2F38d1e46f-d122-4fa3-b130-772169c24466.png" class="crayons-avatar__image" alt="" width="800" height="743"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Raju Dandigam&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/raju_dandigam/a-confidence-score-is-not-a-probability-act-ask-or-abstain-4g3k" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 28&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/raju_dandigam/a-confidence-score-is-not-a-probability-act-ask-or-abstain-4g3k" id="article-link-4619833"&gt;
          A Confidence Score Is Not a Probability: Act, Ask, or Abstain
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/machinelearning"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;machinelearning&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/testing"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;testing&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/architecture"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;architecture&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/raju_dandigam/a-confidence-score-is-not-a-probability-act-ask-or-abstain-4g3k" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;5&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/raju_dandigam/a-confidence-score-is-not-a-probability-act-ask-or-abstain-4g3k#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              4&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>software</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Stop Pushing Secrets to GitHub: Protect Your API Keys Before They Leak</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Tue, 22 Sep 2026 15:30:00 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/stop-pushing-secrets-to-github-protect-your-api-keys-before-they-leak-4pmj</link>
      <guid>https://dev.to/johnnylemonny/stop-pushing-secrets-to-github-protect-your-api-keys-before-they-leak-4pmj</guid>
      <description>&lt;p&gt;You notice the mistake a few seconds after pushing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.env
config.ts
service-account.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;One of those files contains a real credential.&lt;/p&gt;

&lt;p&gt;Your first instinct may be to delete the value, create another commit, and push again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not make that your first response.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The credential may already exist in Git history, a clone, a fork, a pull request, an automated log, or a security alert. Removing it from the latest version of a file does not invalidate it.&lt;/p&gt;

&lt;p&gt;If a real secret reaches GitHub, assume it has been exposed.&lt;/p&gt;

&lt;p&gt;Revoke or rotate it first. Investigate the exposure second. Clean the repository only when necessary. Then add controls that make the same mistake harder to repeat.&lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  
&lt;h2&gt;
  
  
  If you just pushed a real secret
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Revoke or rotate the credential immediately.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Check the provider's access and billing logs.&lt;/li&gt;
&lt;li&gt;Identify every location where the secret appeared.&lt;/li&gt;
&lt;li&gt;Replace it with a newly issued credential stored outside the repository.&lt;/li&gt;
&lt;li&gt;Decide whether Git history must also be rewritten.&lt;/li&gt;
&lt;li&gt;Add preventive controls before continuing normal work.&lt;/li&gt;
&lt;/ol&gt;


&lt;/div&gt;



&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What counts as a secret?&lt;/li&gt;
&lt;li&gt;The correct recovery order&lt;/li&gt;
&lt;li&gt;Why deleting the file is not enough&lt;/li&gt;
&lt;li&gt;Check what Git is already tracking&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;.gitignore&lt;/code&gt; correctly&lt;/li&gt;
&lt;li&gt;Commit &lt;code&gt;.env.example&lt;/code&gt;, not &lt;code&gt;.env&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Enable GitHub's protection&lt;/li&gt;
&lt;li&gt;Store secrets safely in GitHub Actions&lt;/li&gt;
&lt;li&gt;Add local checks before every push&lt;/li&gt;
&lt;li&gt;Use the final checklist&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a id="what-counts-as-a-secret"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What counts as a secret?
&lt;/h2&gt;

&lt;p&gt;A secret is any value that grants access or proves identity and should not be available to everyone who can read the repository.&lt;/p&gt;

&lt;p&gt;Common examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API keys,&lt;/li&gt;
&lt;li&gt;personal access tokens,&lt;/li&gt;
&lt;li&gt;OAuth client secrets,&lt;/li&gt;
&lt;li&gt;database passwords,&lt;/li&gt;
&lt;li&gt;private SSH keys,&lt;/li&gt;
&lt;li&gt;cloud access credentials,&lt;/li&gt;
&lt;li&gt;webhook signing secrets,&lt;/li&gt;
&lt;li&gt;service-account credentials,&lt;/li&gt;
&lt;li&gt;package registry tokens,&lt;/li&gt;
&lt;li&gt;session-signing keys,&lt;/li&gt;
&lt;li&gt;production connection strings.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not every configuration value is a secret. A public API base URL, feature flag name, region, or build mode can usually be stored as ordinary configuration.&lt;/p&gt;

&lt;p&gt;A simple test is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If someone copied this value from a public repository, could that person access data, impersonate a service, trigger an action, or create a cost?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer might be yes, treat the value as sensitive.&lt;/p&gt;

&lt;h3&gt;
  
  
  A secret name is not a secret
&lt;/h3&gt;

&lt;p&gt;This is safe to commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PAYMENT_API_KEY=
DATABASE_URL=
EMAIL_PROVIDER_TOKEN=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is not:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PAYMENT_API_KEY=live_example_value
DATABASE_URL=postgres://user:real-password@host/database
EMAIL_PROVIDER_TOKEN=real_token_value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The first file documents the required configuration. The second file contains credentials that can be used.&lt;/p&gt;

&lt;p&gt;&lt;a id="the-correct-recovery-order"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The correct recovery order
&lt;/h2&gt;

&lt;p&gt;When a secret is exposed, sequence matters.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Revoke or rotate
        ↓
Assess the exposure
        ↓
Replace the credential
        ↓
Remove it from current code
        ↓
Decide whether history cleanup is necessary
        ↓
Add preventive controls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  1. Revoke or rotate the credential
&lt;/h3&gt;

&lt;p&gt;Go to the service that issued the credential and make the exposed value unusable.&lt;/p&gt;

&lt;p&gt;Depending on the provider, this may mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deleting the key,&lt;/li&gt;
&lt;li&gt;rotating the key,&lt;/li&gt;
&lt;li&gt;revoking a token,&lt;/li&gt;
&lt;li&gt;resetting a password,&lt;/li&gt;
&lt;li&gt;disabling a service account,&lt;/li&gt;
&lt;li&gt;replacing a signing secret.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the service supports overlapping credentials, create a replacement, update the application, verify the new credential, and then revoke the exposed one. If immediate abuse is possible, revoke first and accept the temporary interruption.&lt;/p&gt;

&lt;p&gt;GitHub's remediation guidance says leaked secrets should be treated as compromised and revoked or rotated. Deleting the value from the current code is not considered sufficient remediation.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Check the provider's logs
&lt;/h3&gt;

&lt;p&gt;Look for unexpected activity after the earliest possible exposure time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unfamiliar IP addresses,&lt;/li&gt;
&lt;li&gt;unusual API calls,&lt;/li&gt;
&lt;li&gt;new resources,&lt;/li&gt;
&lt;li&gt;unexpected downloads,&lt;/li&gt;
&lt;li&gt;permission changes,&lt;/li&gt;
&lt;li&gt;increased usage,&lt;/li&gt;
&lt;li&gt;billing spikes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The secret provider is the strongest source of truth for whether a credential remains valid and how it was used.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Find every exposed location
&lt;/h3&gt;

&lt;p&gt;The value may appear in more places than the file you first noticed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;older commits,&lt;/li&gt;
&lt;li&gt;another branch,&lt;/li&gt;
&lt;li&gt;an open or closed pull request,&lt;/li&gt;
&lt;li&gt;issue text or comments,&lt;/li&gt;
&lt;li&gt;workflow logs,&lt;/li&gt;
&lt;li&gt;generated artifacts,&lt;/li&gt;
&lt;li&gt;documentation,&lt;/li&gt;
&lt;li&gt;copied sample files,&lt;/li&gt;
&lt;li&gt;forks or local clones.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub secret scanning examines Git history and can also scan other GitHub surfaces such as issues, pull requests, discussions, wikis, and secret gists for supported patterns.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Replace the credential safely
&lt;/h3&gt;

&lt;p&gt;Store the replacement in an appropriate secret manager, deployment platform, local environment, or GitHub Actions secret. Do not paste the replacement into the same tracked file.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Decide whether history cleanup is necessary
&lt;/h3&gt;

&lt;p&gt;Rotating the credential removes its ability to grant access. Rewriting history serves a different purpose: removing the sensitive value from repository history.&lt;/p&gt;

&lt;p&gt;History cleanup may be justified when the exposed material is not easily revocable, contains private data, or must be removed for policy or legal reasons. It is not automatically the first or safest step for every leaked API key.&lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  

&lt;p&gt;&lt;strong&gt;Rotation limits access. History rewriting removes stored copies.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These are different operations, and rotation usually comes first.&lt;/p&gt;


&lt;/div&gt;



&lt;p&gt;&lt;a id="why-deleting-the-file-is-not-enough"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why deleting the file is not enough
&lt;/h2&gt;

&lt;p&gt;Git stores snapshots through commits. If a secret appears in one commit and is deleted in the next, the earlier commit still contains it.&lt;/p&gt;

&lt;p&gt;This sequence does not solve the incident:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git &lt;span class="nb"&gt;rm&lt;/span&gt; .env
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Remove environment file"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It removes &lt;code&gt;.env&lt;/code&gt; from the latest version of the branch, but the exposed credential remains in previous history and is still usable until revoked.&lt;/p&gt;

&lt;p&gt;Deleting and recreating the repository is not a substitute for revoking the credential either. Copies may already exist elsewhere.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why history rewriting requires care
&lt;/h3&gt;

&lt;p&gt;Tools such as &lt;code&gt;git-filter-repo&lt;/code&gt; can remove sensitive paths or values from history, but rewriting shared history has side effects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;commit hashes change,&lt;/li&gt;
&lt;li&gt;collaborators must clean or replace existing clones,&lt;/li&gt;
&lt;li&gt;outdated clones can accidentally restore the secret,&lt;/li&gt;
&lt;li&gt;pull request diffs and comments can be disrupted,&lt;/li&gt;
&lt;li&gt;branch protections may need temporary changes,&lt;/li&gt;
&lt;li&gt;automation that depends on commit hashes may break.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub explicitly recommends coordinating with collaborators and understanding these consequences before rewriting history.&lt;/p&gt;

&lt;p&gt;Do not paste a destructive history-rewrite command from a random snippet and run it against an active repository. Read GitHub's current procedure, back up what must be preserved, coordinate the maintenance window, and verify every branch and tag afterward.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  When is history rewriting worth considering?
  &lt;p&gt;Consider it when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the exposed data cannot be revoked,&lt;/li&gt;
&lt;li&gt;the repository contains a private key or sensitive personal data,&lt;/li&gt;
&lt;li&gt;policy requires removal from all reachable history,&lt;/li&gt;
&lt;li&gt;the value appears across many commits or branches,&lt;/li&gt;
&lt;li&gt;the repository owner has assessed the operational impact,&lt;/li&gt;
&lt;li&gt;collaborators can coordinate replacement or cleanup of clones.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rotation may be enough for a standard API token when the old value is definitely invalid and no separate policy requires complete removal. Make that decision based on the type of data, repository visibility, exposure, and organizational requirements.&lt;/p&gt;



&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;a id="check-what-git-is-already-tracking"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Check what Git is already tracking
&lt;/h2&gt;

&lt;p&gt;A common misunderstanding is that adding a filename to &lt;code&gt;.gitignore&lt;/code&gt; makes Git forget it.&lt;/p&gt;

&lt;p&gt;It does not.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.gitignore&lt;/code&gt; prevents matching &lt;strong&gt;untracked&lt;/strong&gt; files from being added by ordinary commands. It does not stop Git from tracking a file that is already in the index.&lt;/p&gt;

&lt;p&gt;Check whether &lt;code&gt;.env&lt;/code&gt; is tracked:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git ls-files &lt;span class="nt"&gt;--error-unmatch&lt;/span&gt; .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If Git prints &lt;code&gt;.env&lt;/code&gt;, the file is tracked. If it returns an error, the exact path is not tracked in the current index.&lt;/p&gt;

&lt;p&gt;You can search for a wider group of environment files:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git ls-files | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'(^|/)\.env($|\.)'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To stop tracking the current file while keeping the local copy:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;--cached&lt;/span&gt; .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then commit the index change and the updated ignore rule:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add .gitignore
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Stop tracking local environment files"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is a repository-hygiene step. It does &lt;strong&gt;not&lt;/strong&gt; revoke a leaked credential or erase older commits.&lt;/p&gt;
&lt;h3&gt;
  
  
  Check the staged snapshot before committing
&lt;/h3&gt;

&lt;p&gt;Before every sensitive commit, inspect what is about to become part of history:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status &lt;span class="nt"&gt;--short&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git diff &lt;span class="nt"&gt;--cached&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The second command shows the staged diff. This catches many accidental additions before they become commits.&lt;/p&gt;

&lt;p&gt;If the staged diff is large, do not skim it blindly. Split unrelated work into smaller commits so that unexpected configuration files and credentials are easier to notice.&lt;/p&gt;

&lt;p&gt;&lt;a id="use-gitignore-correctly"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Use &lt;code&gt;.gitignore&lt;/code&gt; correctly
&lt;/h2&gt;

&lt;p&gt;A sensible starting point for a repository that uses local environment files is:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Local environment files
.env
.env.*
!.env.example

# Local credentials and certificates
*.pem
*.key
service-account*.json

# Tool-specific local configuration
.envrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Do not copy this block without adapting it.&lt;/p&gt;

&lt;p&gt;For example, a project might intentionally track a public test certificate, a harmless fixture named &lt;code&gt;service-account.example.json&lt;/code&gt;, or a platform-specific environment template. Ignore rules are repository policy, not universal truth.&lt;/p&gt;
&lt;h3&gt;
  
  
  Ignore by purpose, not by panic
&lt;/h3&gt;

&lt;p&gt;Avoid broad rules such as:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*.json
*.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Those rules can hide legitimate source files. A good &lt;code&gt;.gitignore&lt;/code&gt; excludes local or generated data without making important project files invisible.&lt;/p&gt;
&lt;h3&gt;
  
  
  Verify the matching rule
&lt;/h3&gt;

&lt;p&gt;If Git ignores a file and you do not know why, run:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git check-ignore &lt;span class="nt"&gt;-v&lt;/span&gt; .env.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Git will show the ignore file and rule responsible for the match.&lt;/p&gt;
&lt;h3&gt;
  
  
  Remember global ignore files
&lt;/h3&gt;

&lt;p&gt;A developer may also have a global Git ignore file. That can be useful for editor files and system artifacts, but repository-critical safety rules should still live in the repository's own &lt;code&gt;.gitignore&lt;/code&gt; so every contributor receives them.&lt;/p&gt;

&lt;p&gt;&lt;a id="commit-envexample-not-env"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Commit &lt;code&gt;.env.example&lt;/code&gt;, not &lt;code&gt;.env&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;A project needs to document its required configuration without publishing real values.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;.env.example&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Application
APP_ENV=development
APP_PORT=3000

# Database
DATABASE_URL=

# External services
PAYMENT_API_KEY=
EMAIL_PROVIDER_TOKEN=

# Session security
SESSION_SECRET=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then document setup:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The developer fills &lt;code&gt;.env&lt;/code&gt; locally or retrieves values from the team's approved secret manager.&lt;/p&gt;
&lt;h3&gt;
  
  
  Use obviously fake examples
&lt;/h3&gt;

&lt;p&gt;Avoid realistic-looking placeholder credentials. A detector may flag them, and a contributor may not know whether they are safe.&lt;/p&gt;

&lt;p&gt;Prefer:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PAYMENT_API_KEY=replace_with_local_value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;or leave the value blank.&lt;/p&gt;

&lt;p&gt;Do not copy a real key and alter only the last few characters. Even a partially exposed credential may reveal information or be reconstructed incorrectly in logs and screenshots.&lt;/p&gt;
&lt;h3&gt;
  
  
  Fail clearly when required configuration is missing
&lt;/h3&gt;

&lt;p&gt;A missing variable should cause an understandable startup error rather than a mysterious failure later.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requiredVariables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PAYMENT_API_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SESSION_SECRET&lt;/span&gt;&lt;span class="dl"&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;for &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;name&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;requiredVariables&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Missing required environment variable: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This validates presence, not safety. It cannot determine whether a value has been committed elsewhere or granted excessive permissions.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is safe to commit?
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Usually safe
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;variable names,&lt;/li&gt;
&lt;li&gt;empty placeholders,&lt;/li&gt;
&lt;li&gt;public URLs,&lt;/li&gt;
&lt;li&gt;non-sensitive feature flags,&lt;/li&gt;
&lt;li&gt;documented local defaults,&lt;/li&gt;
&lt;li&gt;sample configuration using fake values.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Usually unsafe
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;live API keys,&lt;/li&gt;
&lt;li&gt;access and refresh tokens,&lt;/li&gt;
&lt;li&gt;private keys,&lt;/li&gt;
&lt;li&gt;production database URLs with credentials,&lt;/li&gt;
&lt;li&gt;cloud credentials,&lt;/li&gt;
&lt;li&gt;signing secrets,&lt;/li&gt;
&lt;li&gt;passwords,&lt;/li&gt;
&lt;li&gt;complete service-account files.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Context dependent
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;public client identifiers,&lt;/li&gt;
&lt;li&gt;analytics IDs,&lt;/li&gt;
&lt;li&gt;test credentials,&lt;/li&gt;
&lt;li&gt;webhook URLs,&lt;/li&gt;
&lt;li&gt;certificate files,&lt;/li&gt;
&lt;li&gt;internal hostnames.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A value being visible in frontend code does not automatically make it harmless. Check the provider's security model and intended usage.&lt;/p&gt;

&lt;p&gt;&lt;a id="enable-githubs-protection"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Enable GitHub's protection
&lt;/h2&gt;

&lt;p&gt;GitHub provides two related defenses: &lt;strong&gt;secret scanning&lt;/strong&gt; and &lt;strong&gt;push protection&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Secret scanning
&lt;/h3&gt;

&lt;p&gt;Secret scanning searches for supported credentials in repository history and other supported GitHub content. Public repositories receive secret scanning automatically for free. Availability for private and internal repositories depends on repository ownership, plan, and GitHub Secret Protection settings.&lt;/p&gt;

&lt;p&gt;When an alert identifies a real credential, rotate the credential immediately. A scanner can find an exposure, but it cannot undo access that has already occurred.&lt;/p&gt;
&lt;h3&gt;
  
  
  Push protection
&lt;/h3&gt;

&lt;p&gt;Push protection attempts to stop supported secrets before they reach the repository.&lt;/p&gt;

&lt;p&gt;GitHub documents coverage for several entry paths, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;command-line pushes,&lt;/li&gt;
&lt;li&gt;commits made through the GitHub UI,&lt;/li&gt;
&lt;li&gt;file uploads,&lt;/li&gt;
&lt;li&gt;REST API requests,&lt;/li&gt;
&lt;li&gt;certain GitHub MCP server interactions for public repositories.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;User push protection is enabled by default on GitHub.com and protects pushes of supported secrets to public repositories. Repository-level protection provides broader administrative controls and depends on GitHub Secret Protection.&lt;/p&gt;
&lt;h3&gt;
  
  
  If GitHub blocks your push
&lt;/h3&gt;

&lt;p&gt;Do not automatically bypass the warning.&lt;/p&gt;

&lt;p&gt;Read the detected secret type and every location listed in the error. If the value is real, remove it from all affected commits before pushing again.&lt;/p&gt;

&lt;p&gt;If the secret was introduced in the latest local commit:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Remove the secret from the file first, then stage the correction&lt;/span&gt;
git add path/to/file

git commit &lt;span class="nt"&gt;--amend&lt;/span&gt; &lt;span class="nt"&gt;--no-edit&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Amending changes the commit that introduced the secret instead of adding a second commit that still leaves the original one in the branch history.&lt;/p&gt;

&lt;p&gt;If the secret appears in earlier local commits, resolving the block may require an interactive rebase. Follow GitHub's current instructions and create a backup branch before changing local history.&lt;/p&gt;

&lt;p&gt;Bypass only when you have verified that the value is a false positive, a documented test value, or another value explicitly safe to publish. “I will fix it later” is not a safe default for a live credential.&lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  

&lt;p&gt;Push protection recognizes supported patterns. It is an important safety net, not proof that a repository contains no secrets.&lt;/p&gt;


&lt;/div&gt;



&lt;p&gt;&lt;a id="store-secrets-safely-in-github-actions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Store secrets safely in GitHub Actions
&lt;/h2&gt;

&lt;p&gt;Workflow files are committed to the repository, so never place a credential directly in YAML.&lt;/p&gt;

&lt;p&gt;Wrong:&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="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;Deploy&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;DEPLOY_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;live_token_value&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;./deploy.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Use a GitHub Actions secret:&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="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;Deploy&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;DEPLOY_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DEPLOY_TOKEN }}&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;./deploy.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Create the secret in:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Repository settings
→ Secrets and variables
→ Actions
→ New repository secret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Or use GitHub CLI:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh secret &lt;span class="nb"&gt;set &lt;/span&gt;DEPLOY_TOKEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;GitHub supports secrets at repository, environment, and organization scope. Choose the narrowest scope that matches the workflow.&lt;/p&gt;
&lt;h3&gt;
  
  
  Secrets and variables are not interchangeable
&lt;/h3&gt;

&lt;p&gt;Use a configuration variable for non-sensitive data:&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;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;DEPLOY_REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ vars.DEPLOY_REGION }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Use a secret for sensitive data:&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;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;DEPLOY_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DEPLOY_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;GitHub warns that variables are not masked in build output by default. Sensitive values belong in secrets.&lt;/p&gt;
&lt;h3&gt;
  
  
  Apply least privilege
&lt;/h3&gt;

&lt;p&gt;A deployment credential should not automatically have administrator access to every repository or environment.&lt;/p&gt;

&lt;p&gt;Prefer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;short-lived credentials where available,&lt;/li&gt;
&lt;li&gt;read-only access when write access is unnecessary,&lt;/li&gt;
&lt;li&gt;environment secrets for protected deployments,&lt;/li&gt;
&lt;li&gt;fine-grained tokens over broad personal tokens,&lt;/li&gt;
&lt;li&gt;explicit &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; permissions,&lt;/li&gt;
&lt;li&gt;required reviewers for sensitive environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A minimal workflow permission block might look like:&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;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;read&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Increase permissions only for the job that needs them.&lt;/p&gt;
&lt;h3&gt;
  
  
  Do not rely completely on log redaction
&lt;/h3&gt;

&lt;p&gt;GitHub masks many known secret values, but transformations, structured values, generated tokens, and accidental output can still create risk.&lt;/p&gt;

&lt;p&gt;Avoid commands such as:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DEPLOY_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Do not place JSON or YAML blobs into one secret if individual values can be stored separately. GitHub notes that structured secret values can be harder to redact reliably because masking often depends on exact matches.&lt;/p&gt;

&lt;p&gt;If an unredacted credential reaches a workflow log, delete the affected log and rotate the credential.&lt;/p&gt;

&lt;p&gt;&lt;a id="add-local-checks-before-every-push"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Add local checks before every push
&lt;/h2&gt;

&lt;p&gt;Remote protection is valuable, but earlier feedback is better.&lt;/p&gt;

&lt;p&gt;A local secret scanner can inspect staged content or commits before they leave the workstation. Common open-source options include tools such as Gitleaks and TruffleHog. Select a tool based on your language ecosystem, CI environment, supported patterns, maintenance policy, and false-positive handling.&lt;/p&gt;

&lt;p&gt;The workflow matters more than the brand:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Edit
  ↓
Review staged diff
  ↓
Run local secret scan
  ↓
Commit
  ↓
Run CI scan
  ↓
Push protection
  ↓
Repository secret scanning
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;No layer is complete on its own.&lt;/p&gt;
&lt;h3&gt;
  
  
  Add a pre-commit check carefully
&lt;/h3&gt;

&lt;p&gt;A local hook can scan staged changes, but hooks stored only under &lt;code&gt;.git/hooks&lt;/code&gt; are not automatically shared with every clone. If the team depends on a hook, manage it through a documented tool or repository setup process.&lt;/p&gt;

&lt;p&gt;The hook should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scan only relevant staged content for speed,&lt;/li&gt;
&lt;li&gt;show the file and rule that triggered,&lt;/li&gt;
&lt;li&gt;provide a documented false-positive process,&lt;/li&gt;
&lt;li&gt;fail safely,&lt;/li&gt;
&lt;li&gt;avoid uploading source code to an unapproved service.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Repeat the check in CI
&lt;/h3&gt;

&lt;p&gt;Local hooks can be skipped or misconfigured. CI provides a consistent second boundary for pull requests and protected branches.&lt;/p&gt;

&lt;p&gt;A failing CI scan should explain how to remediate the finding. Security gates that only display “failed” encourage bypasses rather than safe behavior.&lt;/p&gt;
&lt;h2&gt;
  
  
  Avoid these common mistakes
&lt;/h2&gt;
&lt;h3&gt;
  
  
  “It is private, so the key is safe”
&lt;/h3&gt;

&lt;p&gt;Private repositories reduce exposure but do not make hardcoded credentials appropriate. Access can expand, logs can leak, repositories can change visibility, and credentials often outlive the code that contains them.&lt;/p&gt;
&lt;h3&gt;
  
  
  “Base64 hides the value”
&lt;/h3&gt;

&lt;p&gt;Base64 is encoding, not encryption. Anyone with the string can decode it.&lt;/p&gt;
&lt;h3&gt;
  
  
  “The key is only for testing”
&lt;/h3&gt;

&lt;p&gt;Test credentials can still access shared data, trigger paid services, or become production credentials later. Use clearly scoped, disposable values with strict limits.&lt;/p&gt;
&lt;h3&gt;
  
  
  “I deleted the repository”
&lt;/h3&gt;

&lt;p&gt;Deletion does not revoke the credential or delete existing copies.&lt;/p&gt;
&lt;h3&gt;
  
  
  “&lt;code&gt;.gitignore&lt;/code&gt; protects everything”
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;.gitignore&lt;/code&gt; does not remove tracked files, scan arbitrary content, prevent manual force-adds, or invalidate exposed credentials.&lt;/p&gt;
&lt;h3&gt;
  
  
  “Push protection will catch every secret”
&lt;/h3&gt;

&lt;p&gt;Detectors cover many known and generic patterns, but no scanner can identify every custom password, encoded value, or context-specific credential.&lt;/p&gt;

&lt;p&gt;&lt;a id="final-checklist"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Final repository security checklist
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Local development
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Real secrets are stored outside tracked files.&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;.env&lt;/code&gt; and local credential files are ignored.&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;.env.example&lt;/code&gt; contains names and safe placeholders only.&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;git diff --cached&lt;/code&gt; is reviewed before sensitive commits.&lt;/li&gt;
&lt;li&gt;[ ] Required configuration fails with a clear startup error.&lt;/li&gt;
&lt;li&gt;[ ] Local scanning is documented and repeatable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  GitHub repository
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Repository visibility is intentional.&lt;/li&gt;
&lt;li&gt;[ ] Secret scanning availability and alerts have been reviewed.&lt;/li&gt;
&lt;li&gt;[ ] Push protection is enabled where available.&lt;/li&gt;
&lt;li&gt;[ ] Bypass permissions are limited and monitored.&lt;/li&gt;
&lt;li&gt;[ ] Security alerts have a named owner.&lt;/li&gt;
&lt;li&gt;[ ] Contributors know how to report an accidental exposure.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  GitHub Actions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Sensitive values use Actions secrets, not workflow literals.&lt;/li&gt;
&lt;li&gt;[ ] Non-sensitive configuration uses variables.&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; permissions are minimal.&lt;/li&gt;
&lt;li&gt;[ ] Deployment secrets use protected environments where appropriate.&lt;/li&gt;
&lt;li&gt;[ ] Workflows do not print secrets or transformed secret values.&lt;/li&gt;
&lt;li&gt;[ ] Third-party actions are reviewed and pinned according to team policy.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Incident response
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] The exposed credential is revoked or rotated first.&lt;/li&gt;
&lt;li&gt;[ ] Provider logs and billing are reviewed.&lt;/li&gt;
&lt;li&gt;[ ] Every known location is identified.&lt;/li&gt;
&lt;li&gt;[ ] History rewriting is treated as a coordinated operation.&lt;/li&gt;
&lt;li&gt;[ ] Existing clones are addressed after a rewrite.&lt;/li&gt;
&lt;li&gt;[ ] Preventive controls are added after the incident.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;The most dangerous misconception is that removing a key from the latest file removes the exposure.&lt;/p&gt;

&lt;p&gt;It does not.&lt;/p&gt;

&lt;p&gt;A safer response is straightforward:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Assume exposure
      ↓
Revoke or rotate
      ↓
Check usage
      ↓
Replace securely
      ↓
Clean current code
      ↓
Evaluate history cleanup
      ↓
Strengthen prevention
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The strongest setup is also layered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.gitignore&lt;/code&gt; keeps ordinary local files out of new commits,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.env.example&lt;/code&gt; documents configuration safely,&lt;/li&gt;
&lt;li&gt;staged-diff review catches mistakes before commit,&lt;/li&gt;
&lt;li&gt;local and CI scanning provide early feedback,&lt;/li&gt;
&lt;li&gt;push protection blocks many supported secrets,&lt;/li&gt;
&lt;li&gt;secret scanning finds supported exposures,&lt;/li&gt;
&lt;li&gt;least-privilege credentials reduce the impact of a mistake.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these controls replaces the others.&lt;/p&gt;

&lt;p&gt;If you discover a live secret in a repository, do not spend the first minutes trying to make the commit disappear. Make the credential useless first.&lt;/p&gt;

&lt;p&gt;What is the most useful secret-protection check you have added to a project?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/code-security/tutorials/remediate-leaked-secrets/remediating-a-leaked-secret" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Read GitHub's secret remediation guide&lt;/a&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Sources and further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Docs:&lt;/strong&gt; &lt;a href="https://docs.github.com/en/code-security/tutorials/remediate-leaked-secrets/remediating-a-leaked-secret" rel="noopener noreferrer"&gt;Remediating a leaked secret in your repository&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Docs:&lt;/strong&gt; &lt;a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository" rel="noopener noreferrer"&gt;Removing sensitive data from a repository&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Docs:&lt;/strong&gt; &lt;a href="https://docs.github.com/en/code-security/concepts/secret-security/push-protection" rel="noopener noreferrer"&gt;Push protection&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Docs:&lt;/strong&gt; &lt;a href="https://docs.github.com/code-security/secret-scanning/working-with-secret-scanning-and-push-protection/working-with-push-protection-from-the-command-line" rel="noopener noreferrer"&gt;Working with push protection from the command line&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Docs:&lt;/strong&gt; &lt;a href="https://docs.github.com/en/code-security/concepts/secret-security/secret-scanning" rel="noopener noreferrer"&gt;Secret scanning&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Docs:&lt;/strong&gt; &lt;a href="https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets" rel="noopener noreferrer"&gt;Using secrets in GitHub Actions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Docs:&lt;/strong&gt; &lt;a href="https://docs.github.com/en/actions/reference/security/secure-use" rel="noopener noreferrer"&gt;Secure use reference for GitHub Actions&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&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;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%2F96a5e915-2968-4aeb-8d85-d9206cb3f703.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>github</category>
      <category>security</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
