<?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: ronnie0297-stack</title>
    <description>The latest articles on DEV Community by ronnie0297-stack (@ronnie0297-stack).</description>
    <link>https://dev.to/ronnie0297-stack</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4135981%2F1e0d510f-b8ad-4b53-8d3d-5737bb695125.png</url>
      <title>DEV Community: ronnie0297-stack</title>
      <link>https://dev.to/ronnie0297-stack</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ronnie0297-stack"/>
    <language>en</language>
    <item>
      <title>Catch runaway AI agent costs before merge with a GitHub Action</title>
      <dc:creator>ronnie0297-stack</dc:creator>
      <pubDate>Mon, 21 Sep 2026 15:10:20 +0000</pubDate>
      <link>https://dev.to/ronnie0297-stack/catch-runaway-ai-agent-costs-before-merge-with-a-github-action-3h1k</link>
      <guid>https://dev.to/ronnie0297-stack/catch-runaway-ai-agent-costs-before-merge-with-a-github-action-3h1k</guid>
      <description>&lt;p&gt;AI cost control usually begins after deployment: a provider dashboard reports what has already been spent. But several expensive failure modes are visible in code before a pull request is merged.&lt;/p&gt;

&lt;p&gt;Consider a tool-using Vercel AI SDK call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;streamText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gpt-5.4-mini&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;search&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;Two limits are missing. There is no output-token ceiling, and the tool loop has no stopping condition. Even if the prompt looks harmless, neither the worst-case response size nor the maximum number of steps is explicit.&lt;/p&gt;

&lt;p&gt;A bounded version makes both decisions reviewable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;streamText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gpt-5.4-mini&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;maxOutputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;stopWhen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;stepCountIs&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="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;search&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;
  
  
  Turning the check into a budget gate
&lt;/h2&gt;

&lt;p&gt;Code alone cannot predict a bill. A useful estimate also needs traffic assumptions. Keep those assumptions in the repository so reviewers can challenge them:&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;"monthlyCallsPerSite"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"assumedInputTokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"assumedOutputTokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"warnMonthlyCost"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"failMonthlyCost"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"failOnUnboundedTools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;Then estimate each call site with a deliberately simple formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;monthly cost = monthly calls ×
  ((input tokens × input price) + (maximum output tokens × output price))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The estimate is not an invoice prediction. It is a consistent review signal. A changed model, increased output ceiling, or new call site produces a visible change before production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it in GitHub Actions
&lt;/h2&gt;

&lt;p&gt;I built an open-source Action called AICostFence to automate this check:&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;AI cost check&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pull_request&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;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cost-check&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@v4&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;ronnie0297-stack/aicostfence@v0.1.0&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It scans locally on the GitHub runner, posts one updated pull-request report, and can fail the check when a configured guard is violated. It does not call an AI model or send source code to a model provider.&lt;/p&gt;

&lt;p&gt;The current release intentionally supports a narrow surface: JavaScript/TypeScript projects using the Vercel AI SDK's &lt;code&gt;generateText&lt;/code&gt;, &lt;code&gt;streamText&lt;/code&gt;, &lt;code&gt;generateObject&lt;/code&gt;, or &lt;code&gt;streamObject&lt;/code&gt; calls. That makes the analysis deterministic while the early workflow is validated.&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/ronnie0297-stack/aicostfence" rel="noopener noreferrer"&gt;https://github.com/ronnie0297-stack/aicostfence&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Marketplace: &lt;a href="https://github.com/marketplace/actions/aicostfence" rel="noopener noreferrer"&gt;https://github.com/marketplace/actions/aicostfence&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;npm: &lt;a href="https://www.npmjs.com/package/aicostfence" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/aicostfence&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What cost control would be most useful in your pull requests: model-swap deltas, provider-specific checks, or organization-wide policies?&lt;/p&gt;

&lt;h2&gt;
  
  
  Try the working demo
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/ronnie0297-stack/aicostfence/tree/main/demo" rel="noopener noreferrer"&gt;before-and-after demo&lt;/a&gt; runs the published npm package against risky code and its corrected version. It verifies that the first scan fails and the second passes, with no API key or model charges. &lt;a href="https://github.com/ronnie0297-stack/aicostfence/actions/workflows/demo.yml" rel="noopener noreferrer"&gt;See the GitHub verification runs&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ronnie0297-stack/aicostfence.git
&lt;span class="nb"&gt;cd &lt;/span&gt;aicostfence/demo
npm ci &lt;span class="nt"&gt;--ignore-scripts&lt;/span&gt;
npm run demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requires Node.js 20 or newer. The demo uses bundled fallback prices for reproducible results. Version 0.1.0 does not include step multiplication, retries, or paid tools in its estimate, and it does not resolve wrappers or imported defaults. Passing the check is not a guarantee of a safe bill.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>githubactions</category>
      <category>typescript</category>
      <category>finops</category>
    </item>
  </channel>
</rss>
