<?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: houseofmvps</title>
    <description>The latest articles on DEV Community by houseofmvps (@houseofmvps).</description>
    <link>https://dev.to/houseofmvps</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3861236%2Fec58da21-d12f-4982-b06e-b72b8f0ffea5.png</url>
      <title>DEV Community: houseofmvps</title>
      <link>https://dev.to/houseofmvps</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/houseofmvps"/>
    <language>en</language>
    <item>
      <title>I stopped 40% of my payment failures before they happened with one Stripe API call and a cron job</title>
      <dc:creator>houseofmvps</dc:creator>
      <pubDate>Thu, 16 Apr 2026 17:24:00 +0000</pubDate>
      <link>https://dev.to/houseofmvps/i-stopped-40-of-my-payment-failures-before-they-happened-with-one-stripe-api-call-and-a-cron-job-53aa</link>
      <guid>https://dev.to/houseofmvps/i-stopped-40-of-my-payment-failures-before-they-happened-with-one-stripe-api-call-and-a-cron-job-53aa</guid>
      <description>&lt;h2&gt;
  
  
  Stop Churn &lt;em&gt;Before&lt;/em&gt; It Happens: The Highest-ROI Dunning Move Most SaaS Founders Ignore
&lt;/h2&gt;

&lt;p&gt;Most dunning advice starts &lt;strong&gt;after a payment fails&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Send an email.&lt;br&gt;
Retry the charge.&lt;br&gt;
Escalate urgency.&lt;/p&gt;

&lt;p&gt;All good advice.&lt;/p&gt;

&lt;p&gt;But none of it addresses the highest-leverage move in churn recovery:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Prevent the payment from failing in the first place.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  The Real Problem: Expired Cards
&lt;/h2&gt;

&lt;p&gt;The single biggest preventable cause of payment failure?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expired credit cards.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~10–12% of all declines come directly from expired cards&lt;/li&gt;
&lt;li&gt;~30% of cards are reissued annually (new number, same user)&lt;/li&gt;
&lt;li&gt;Customers don’t update their details proactively&lt;/li&gt;
&lt;li&gt;The next billing cycle hits → &lt;strong&gt;payment fails → dunning begins&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the time you enter dunning, you’re already reacting.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Fix Is Stupidly Simple
&lt;/h2&gt;

&lt;p&gt;Stripe already gives you everything you need.&lt;/p&gt;

&lt;p&gt;Every &lt;code&gt;PaymentMethod&lt;/code&gt; object contains:&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;paymentMethod&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;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pm_xxx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// paymentMethod.card.exp_month → 7&lt;/span&gt;
&lt;span class="c1"&gt;// paymentMethod.card.exp_year  → 2026&lt;/span&gt;
&lt;span class="c1"&gt;// paymentMethod.card.last4     → "4242"&lt;/span&gt;
&lt;span class="c1"&gt;// paymentMethod.card.brand     → "visa"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You just need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pull all active subscribers&lt;/li&gt;
&lt;li&gt;Check card expiry dates&lt;/li&gt;
&lt;li&gt;Notify users &lt;strong&gt;before&lt;/strong&gt; failure happens&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Step 1: Find Expiring Cards
&lt;/h2&gt;

&lt;p&gt;Here’s a working skeleton:&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;stripe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stripe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;STRIPE_SECRET_KEY&lt;/span&gt;&lt;span class="p"&gt;);&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;findExpiringCards&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;now&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;Date&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;currentMonth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentYear&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFullYear&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;target&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&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;targetMonth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targetYear&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFullYear&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;hasMore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;startingAfter&lt;/span&gt; &lt;span class="o"&gt;=&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;expiring&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasMore&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;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;limit&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&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;startingAfter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;starting_after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;startingAfter&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;subs&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;stripe&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;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&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;sub&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;subs&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;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;default_payment_method&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;pm&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;pm&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;exp_month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;exp_year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;last4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;brand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;card&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;expiresThisMonth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nx"&gt;exp_year&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;currentYear&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;exp_month&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;currentMonth&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;expiresNextMonth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nx"&gt;exp_year&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;targetYear&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;exp_month&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;targetMonth&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;expiresThisMonth&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;expiresNextMonth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;expiring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;subscriptionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sub&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="nx"&gt;last4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;exp_month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;exp_year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="c1"&gt;// fetch separately&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;hasMore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;subs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;has_more&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;subs&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;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;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="nx"&gt;startingAfter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;subs&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;span class="nx"&gt;subs&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;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&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;return&lt;/span&gt; &lt;span class="nx"&gt;expiring&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;
  
  
  Step 2: Give Them a Frictionless Fix
&lt;/h2&gt;

&lt;p&gt;Don’t send users to login pages.&lt;br&gt;
Don’t create unnecessary steps.&lt;/p&gt;

&lt;p&gt;Generate a &lt;strong&gt;direct update link&lt;/strong&gt; using Stripe Customer Portal:&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;getUpdateLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customerId&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;session&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;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;billingPortal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessions&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;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;return_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://yourapp.com/billing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;flow_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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payment_method_update&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="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;session&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This link is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pre-authenticated&lt;/li&gt;
&lt;li&gt;Short-lived&lt;/li&gt;
&lt;li&gt;One-click → update → done&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 3: The Email (Don’t Overthink It)
&lt;/h2&gt;

&lt;p&gt;Plain text wins.&lt;/p&gt;

&lt;p&gt;No templates. No branding. No fluff.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Subject: Your card ending in 4242 expires soon

Hey [name],

Quick heads up — the Visa ending in 4242 that you have on file for [product name] expires next month.

If you update it before your next billing date ([date]), everything stays seamless. Takes about 30 seconds:

[Update your card →]

If you've already received a new card from your bank, it likely has a different number even though the old one still works until expiry.

Thanks,  
[your name]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Run It Daily
&lt;/h2&gt;

&lt;p&gt;Set it up as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cron job&lt;/li&gt;
&lt;li&gt;Scheduled function&lt;/li&gt;
&lt;li&gt;Background worker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cadence:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Day -30 → first email&lt;/li&gt;
&lt;li&gt;Day -7 → reminder (optional)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why This Works
&lt;/h2&gt;

&lt;p&gt;You’re intercepting failure &lt;strong&gt;before it exists&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Compare:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Timing&lt;/th&gt;
&lt;th&gt;Conversion Difficulty&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Traditional dunning&lt;/td&gt;
&lt;td&gt;After failure&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pre-expiry alert&lt;/td&gt;
&lt;td&gt;Before failure&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The user hasn’t experienced a problem yet — so fixing it feels trivial.&lt;/p&gt;




&lt;h2&gt;
  
  
  Want This Without Building It Yourself?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;If you’d rather not wire all of this up manually plus handle edge cases, reminders, and multiple recovery layers that’s exactly why I built SaveMRR.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It doesn’t just catch expiring cards.&lt;br&gt;
It runs multiple recovery mechanisms in the background so you’re not leaking revenue silently every month.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;🔍 What it does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pre-expiry detection (like the flow above)&lt;/li&gt;
&lt;li&gt;Smart recovery sequences&lt;/li&gt;
&lt;li&gt;Continuous monitoring of failed payments&lt;/li&gt;
&lt;li&gt;Zero maintenance once set up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pricing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$19/month — built for bootstrapped SaaS founders&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Free Revenue Scan:&lt;/p&gt;

&lt;p&gt;Connects to Stripe (read-only access)&lt;br&gt;
Uses restricted API keys&lt;br&gt;
AES-256 encrypted&lt;br&gt;
Shows exactly how much revenue you’re losing&lt;/p&gt;

&lt;p&gt;Takes ~60 seconds.&lt;br&gt;
No credit card required.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://savemrr.co" rel="noopener noreferrer"&gt;https://savemrr.co&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What About Stripe Account Updater?
&lt;/h2&gt;

&lt;p&gt;Stripe’s Account Updater helps by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatically updating card details when banks reissue cards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It’s not universal&lt;/li&gt;
&lt;li&gt;Depends on issuer + network support&lt;/li&gt;
&lt;li&gt;Misses a non-trivial percentage&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Pre-expiry emails cover the gap.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;If you’re running a SaaS on Stripe and not doing this:&lt;/p&gt;

&lt;p&gt;You are &lt;strong&gt;choosing&lt;/strong&gt; to lose recoverable revenue every month.&lt;/p&gt;

&lt;p&gt;This is not an optimization.&lt;br&gt;
This is baseline infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Take
&lt;/h2&gt;

&lt;p&gt;Before you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Optimize retry logic&lt;/li&gt;
&lt;li&gt;Add more dunning emails&lt;/li&gt;
&lt;li&gt;Build complex churn flows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fix the simplest leak:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Catch expiring cards before they fail.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Everything else comes after.&lt;/p&gt;




</description>
      <category>stripe</category>
      <category>javascript</category>
      <category>saas</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Your SaaS is leaking money right now. Here's how to find it in 60 seconds.</title>
      <dc:creator>houseofmvps</dc:creator>
      <pubDate>Wed, 15 Apr 2026 19:55:56 +0000</pubDate>
      <link>https://dev.to/houseofmvps/your-saas-is-leaking-money-right-now-heres-how-to-find-it-in-60-seconds-3kfa</link>
      <guid>https://dev.to/houseofmvps/your-saas-is-leaking-money-right-now-heres-how-to-find-it-in-60-seconds-3kfa</guid>
      <description>&lt;h2&gt;
  
  
  I’m going to show you something that most SaaS founders have never checked
&lt;/h2&gt;

&lt;p&gt;It takes 60 seconds and it might ruin your morning.&lt;/p&gt;




&lt;p&gt;Open your Stripe dashboard. Go to Payments. Filter by status: Failed. Set the date range to the last 90 days.&lt;/p&gt;

&lt;p&gt;Add up the numbers.&lt;/p&gt;

&lt;p&gt;That total?&lt;/p&gt;

&lt;p&gt;That’s revenue you billed for but never collected. Customers who didn’t cancel their payments just broke. And unless you did something about each one individually, that money is gone. &lt;/p&gt;




&lt;h2&gt;
  
  
  I’ve been building a payment recovery tool
&lt;/h2&gt;

&lt;p&gt;For the past few months, I’ve talked to dozens of SaaS founders.&lt;/p&gt;

&lt;p&gt;Almost none of them had ever run this filter.&lt;/p&gt;

&lt;p&gt;The number is usually between &lt;strong&gt;5% and 12% of total billings&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On &lt;strong&gt;$20K MRR&lt;/strong&gt;, that’s &lt;strong&gt;$1,000 to $2,400 every single month&lt;/strong&gt; that silently disappears.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;Subscription payments fail for a handful of reasons, and they’re more mundane than you’d expect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Insufficient funds account for roughly 44–47% of all card declines.&lt;/strong&gt;&lt;br&gt;
This is the biggest bucket by far. The customer’s card is valid, they haven’t cancelled, they just don’t have enough balance on billing day. This is especially common with debit cards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fraud detection flags cause another 15–20%.&lt;/strong&gt;&lt;br&gt;
Most of these are false positives the bank’s algorithm flagged a recurring charge as suspicious. The customer has no idea this happened.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expired or reissued cards make up about 10–12%.&lt;/strong&gt;&lt;br&gt;
Visa says roughly 30% of all cards get reissued in any given year. When that happens, the old card number on file stops working. Unless the customer proactively updates it (they won’t), the next charge fails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rest&lt;/strong&gt; is a mix of card limits, incorrect details from card reissuance, processor errors, and the dreaded “do not honor” generic decline that issuers throw when they don’t want to tell you the real reason.&lt;/p&gt;


&lt;h2&gt;
  
  
  What Stripe does about it and what it doesn’t
&lt;/h2&gt;

&lt;p&gt;Stripe has a feature called Smart Retries. If you’re on Stripe Billing, it’s on by default. It uses an ML model trained on hundreds of billions of data points across the entire Stripe network to figure out the best time to retry a failed charge.&lt;/p&gt;

&lt;p&gt;The model considers over 500 signals card issuer patterns, time of day, day of week, customer activity, even debit card payday cycles.&lt;/p&gt;

&lt;p&gt;It’s genuinely impressive engineering.&lt;/p&gt;

&lt;p&gt;Stripe says it recovers &lt;strong&gt;$9 in revenue for every $1 spent on Billing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But here’s what Smart Retries don’t do:&lt;/p&gt;

&lt;p&gt;They don’t email your customer.&lt;br&gt;
They don’t send an SMS.&lt;br&gt;
They don’t alert you that a VIP account is past due.&lt;br&gt;
They don’t warn a customer whose card is about to expire.&lt;br&gt;
They don’t put up an in-app banner asking the customer to update their payment method.&lt;/p&gt;

&lt;p&gt;Smart Retries handle the charge retry.&lt;/p&gt;

&lt;p&gt;Everything else the outbound communication, the pre-dunning, the urgency creation, the one-click card update links is on you.&lt;/p&gt;

&lt;p&gt;And “on you” usually means &lt;strong&gt;“doesn’t happen.”&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The gap is where the money lives
&lt;/h2&gt;

&lt;p&gt;The gap between what Stripe recovers automatically and what a proper dunning system recovers is where your lost revenue sits.&lt;/p&gt;

&lt;p&gt;Third-party sources estimate Smart Retries alone recover somewhere around &lt;strong&gt;38% of failed payments&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The best dedicated dunning systems push recovery to &lt;strong&gt;50–70%&lt;/strong&gt;, according to Churn Buster’s published data across their client base.&lt;/p&gt;

&lt;p&gt;That &lt;strong&gt;20–30 percentage point gap&lt;/strong&gt;, applied to your specific failed payment volume, is the dollar amount you’re currently leaving on the table every month.&lt;/p&gt;


&lt;h2&gt;
  
  
  Here’s the math on a real SaaS business
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;$20,000 MRR&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;About &lt;strong&gt;13% of recurring charges decline monthly&lt;/strong&gt; (Recurly’s benchmark across 1,200 sites).&lt;/p&gt;

&lt;p&gt;That’s &lt;strong&gt;$2,600/month in failed charges.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Stripe Smart Retries recover ~38%, getting back roughly &lt;strong&gt;$988&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The remaining &lt;strong&gt;$1,612&lt;/strong&gt; needs human-scale intervention:&lt;br&gt;
an email, an SMS, a warning before the card expires.&lt;/p&gt;

&lt;p&gt;A proper dunning system recovering at 55% would get back &lt;strong&gt;$1,430&lt;/strong&gt; — an incremental &lt;strong&gt;$442/month&lt;/strong&gt; over Stripe alone.&lt;/p&gt;

&lt;p&gt;That’s &lt;strong&gt;$5,304/year&lt;/strong&gt; in revenue that was already yours, from customers who never wanted to leave.&lt;/p&gt;


&lt;h2&gt;
  
  
  What you can do right now, for free
&lt;/h2&gt;

&lt;p&gt;Before you buy any tool, do three things:&lt;/p&gt;
&lt;h3&gt;
  
  
  First
&lt;/h3&gt;

&lt;p&gt;Run that Stripe filter I mentioned.&lt;/p&gt;

&lt;p&gt;Know your number.&lt;/p&gt;

&lt;p&gt;It’s the most important metric in your business that you’re not tracking.&lt;/p&gt;


&lt;h3&gt;
  
  
  Second
&lt;/h3&gt;

&lt;p&gt;Set up a webhook listener for &lt;code&gt;invoice.payment_failed&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When it fires, send an email from your own domain&lt;br&gt;
(not noreply@, not billing@ a real address that someone reads)&lt;/p&gt;

&lt;p&gt;Include a link to Stripe’s Customer Portal where the customer can update their card.&lt;/p&gt;

&lt;p&gt;You can generate a portal session URL via:&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;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;billingPortal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessions&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Takes maybe an afternoon of work.&lt;/p&gt;




&lt;h3&gt;
  
  
  Third
&lt;/h3&gt;

&lt;p&gt;Pull card expiry data from the Stripe API.&lt;/p&gt;

&lt;p&gt;The PaymentMethod object has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;card.exp_month&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;card.exp_year&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Write a daily cron that checks for cards expiring in the next 30 days and emails those customers a heads up.&lt;/p&gt;

&lt;p&gt;This alone prevents a meaningful chunk of failures from ever happening.&lt;/p&gt;




&lt;h2&gt;
  
  
  If you want this fully automated
&lt;/h2&gt;

&lt;p&gt;I built &lt;strong&gt;SaveMRR&lt;/strong&gt; for exactly this use case.&lt;/p&gt;

&lt;p&gt;$19/month for bootstrapped SaaS founders.&lt;/p&gt;

&lt;p&gt;There’s a free Revenue Scan that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connects to your Stripe (read-only)&lt;/li&gt;
&lt;li&gt;Uses restricted API keys&lt;/li&gt;
&lt;li&gt;AES-256 encrypted&lt;/li&gt;
&lt;li&gt;Shows you the exact dollar amount you’re losing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Takes 60 seconds. No credit card needed.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://savemrr.co" rel="noopener noreferrer"&gt;https://savemrr.co&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  But honestly
&lt;/h2&gt;

&lt;p&gt;Start with the Stripe filter.&lt;/p&gt;

&lt;p&gt;The number will motivate everything else.&lt;/p&gt;




</description>
      <category>stripe</category>
      <category>saas</category>
      <category>webdev</category>
      <category>startup</category>
    </item>
    <item>
      <title>Slash 90% of Tokens Per Session With This Pre-Compiled Wiki (Karpathy Inspired Workflow)</title>
      <dc:creator>houseofmvps</dc:creator>
      <pubDate>Thu, 09 Apr 2026 23:04:06 +0000</pubDate>
      <link>https://dev.to/houseofmvps/karpathy-called-it-context-engineering-prompt-engineering-i-built-a-tool-that-does-it-4d38</link>
      <guid>https://dev.to/houseofmvps/karpathy-called-it-context-engineering-prompt-engineering-i-built-a-tool-that-does-it-4d38</guid>
      <description>&lt;p&gt;Last June, Karpathy posted something that got 2.3 million views. He said context engineering matters more than prompt engineering specifically, "the delicate art and science of filling the context window with just the right information for the next step."&lt;/p&gt;

&lt;p&gt;Then last week he posted about building structured markdown knowledge bases that LLMs can reason over. Also went viral.&lt;/p&gt;

&lt;p&gt;Both ideas point at the same problem: your AI is only as good as the context you give it. And right now, most of us are giving it terrible context.&lt;/p&gt;

&lt;p&gt;the problem nobody's measuring&lt;/p&gt;

&lt;p&gt;Every time you start a Claude Code session, it spends the first chunk of time just figuring out your project. Reading files. Grepping for routes. Opening package.json. Exploring the import graph. Finding your schema. Checking your env vars.&lt;/p&gt;

&lt;p&gt;I started measuring how many tokens this costs. On a real 92-file monorepo (Hono + Drizzle, 4 workspaces): ~66,000 tokens. Every session. Not cached between sessions.&lt;/p&gt;

&lt;p&gt;On a 53-file project: ~46,000 tokens. On a 40-file project: ~26,000.&lt;/p&gt;

&lt;p&gt;That's your AI burning through your context window (the "RAM" in Karpathy's analogy) just to understand the project before it does anything you actually asked for.&lt;/p&gt;

&lt;p&gt;what context engineering looks like in practice&lt;/p&gt;

&lt;p&gt;If you follow Karpathy's framing, the solution is obvious: don't let your AI waste context exploring. Pre-compile the context it needs and hand it over at session start.&lt;/p&gt;

&lt;p&gt;That's what I built. &lt;code&gt;npx codesight&lt;/code&gt; scans your codebase via AST parsing and generates a structured context map (routes, schema, components, dependency graph, env vars, middleware, hot files)in one markdown file your AI reads immediately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx codesight
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One command. Zero dependencies. It borrows TypeScript from your own node_modules for the compiler API. Falls back to regex for non-TS projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  the numbers
&lt;/h2&gt;

&lt;p&gt;Real production codebases. Not toy demos.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Files&lt;/th&gt;
&lt;th&gt;codesight Output&lt;/th&gt;
&lt;th&gt;Manual Exploration&lt;/th&gt;
&lt;th&gt;Reduction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SaaS A (Hono + Drizzle monorepo)&lt;/td&gt;
&lt;td&gt;92&lt;/td&gt;
&lt;td&gt;5,129 tokens&lt;/td&gt;
&lt;td&gt;~66,040 tokens&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12.9x&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SaaS B (raw HTTP + Drizzle)&lt;/td&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;td&gt;3,945 tokens&lt;/td&gt;
&lt;td&gt;~46,020 tokens&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11.7x&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SaaS C (Hono + Drizzle, 3 workspaces)&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;2,865 tokens&lt;/td&gt;
&lt;td&gt;~26,130 tokens&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9.1x&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Average: 11.2x. Your AI reads 3-5K tokens of structured context instead of burning 26-66K tokens exploring.&lt;/p&gt;

&lt;h2&gt;
  
  
  why AST matters
&lt;/h2&gt;

&lt;p&gt;Regex-based tools guess at your code structure. AST parsing actually understands it.&lt;/p&gt;

&lt;p&gt;When TypeScript is in your project, codesight uses the real TypeScript compiler API. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Follows &lt;code&gt;router.use('/prefix', subRouter)&lt;/code&gt; chains (regex misses nested routers)&lt;/li&gt;
&lt;li&gt;Combines NestJS &lt;code&gt;@Controller('users')&lt;/code&gt; + &lt;code&gt;@Get(':id')&lt;/code&gt; into &lt;code&gt;/users/:id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Parses tRPC &lt;code&gt;router({ users: userRouter })&lt;/code&gt; nesting correctly&lt;/li&gt;
&lt;li&gt;Extracts Drizzle field types from &lt;code&gt;.primaryKey().notNull()&lt;/code&gt; chains&lt;/li&gt;
&lt;li&gt;Detects middleware in route handler chains: &lt;code&gt;app.get('/path', auth, handler)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Filters out false positives like &lt;code&gt;c.get('userId')&lt;/code&gt; that regex would match as routes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Zero false positives across all three benchmark projects.&lt;/p&gt;

&lt;p&gt;25+ frameworks detected. 8 ORMs parsed. React/Vue/Svelte components with props.&lt;/p&gt;

&lt;h2&gt;
  
  
  blast radius — context engineering for changes
&lt;/h2&gt;

&lt;p&gt;Karpathy's framing isn't just about initial context. It's about giving your AI the right information for "the next step." When the next step is changing a file, your AI needs to know what breaks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx codesight &lt;span class="nt"&gt;--blast&lt;/span&gt; src/db/index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;BFS through the import graph. Shows every transitively affected file, route, and model.&lt;/p&gt;

&lt;p&gt;On BuildRadar, changing the database module correctly identified 10 affected files, 33 routes, and all 12 models. Three hops deep.&lt;/p&gt;

&lt;p&gt;Your AI reads this before touching the file. That's context engineering applied to refactoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  the wiki layer (Karpathy's latest idea, automated)
&lt;/h2&gt;

&lt;p&gt;Karpathy's April 3rd post was about structured markdown wikis that LLMs can reason over. codesight v1.6.2 added &lt;code&gt;--wiki&lt;/code&gt; which does exactly this for your codebase:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;It generates a wiki knowledge base in &lt;code&gt;.codesight/wiki/&lt;/code&gt; — an index.md (200 tokens) plus individual articles per topic. Your AI reads the index at session start, then pulls the one relevant article for each question.&lt;/p&gt;

&lt;p&gt;Without codesight: AI reads 26-66K tokens exploring.&lt;br&gt;
With codesight: AI reads 3-5K tokens (the full map).&lt;br&gt;
With &lt;code&gt;--wiki&lt;/code&gt;: AI reads ~200 tokens at start, then ~160-350 per question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Combined reduction: ~91x.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  it generates context for everything
&lt;/h2&gt;

&lt;p&gt;One command creates context files for every major AI tool:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CLAUDE.md&lt;/code&gt; for Claude Code&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.cursorrules&lt;/code&gt; for Cursor&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;codex.md&lt;/code&gt; for OpenAI Codex&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AGENTS.md&lt;/code&gt; for Codex agents&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.github/copilot-instructions.md&lt;/code&gt; for GitHub Copilot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each pre-filled with your actual project structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP server mode
&lt;/h2&gt;



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

&lt;/div&gt;



&lt;p&gt;Runs as a Model Context Protocol server. Your AI queries specific context on demand instead of loading everything. Session caching — first call scans, subsequent calls return instantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  the relationship to caveman mode
&lt;/h2&gt;

&lt;p&gt;The caveman prompt trick reduces output tokens (what the AI says back).&lt;/p&gt;

&lt;p&gt;codesight reduces input/exploration tokens (what the AI reads to understand your project).&lt;/p&gt;

&lt;p&gt;Caveman = make the AI talk less.&lt;br&gt;
codesight = give the AI exactly what it needs to know.&lt;/p&gt;

&lt;p&gt;They're complementary. Use both.&lt;/p&gt;

&lt;h2&gt;
  
  
  try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx codesight
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero deps. MIT. ~200ms scan time. Works with any Node.js project (and has regex fallback for Python, Go, Ruby, Rust, Java, Kotlin, Elixir, PHP).&lt;/p&gt;

&lt;p&gt;(&lt;a href="https://github.com/Houseofmvps/codesight" rel="noopener noreferrer"&gt;https://github.com/Houseofmvps/codesight&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;If it saves you tokens, a star helps others find it too.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Karpathy defined the skill. This tool automates it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built by &lt;a href="https://x.com/kaileskkhumar" rel="noopener noreferrer"&gt;Kailesk Khumar&lt;/a&gt;, solo founder of &lt;a href="https://houseofmvps.com" rel="noopener noreferrer"&gt;houseofmvps.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claude</category>
      <category>ai</category>
      <category>claudecode</category>
      <category>vibecoding</category>
    </item>
  </channel>
</rss>
