<?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: Kento Nishio</title>
    <description>The latest articles on DEV Community by Kento Nishio (@ken2403).</description>
    <link>https://dev.to/ken2403</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%2F4036971%2F0b9bfead-ff20-4cdc-8893-0af1f6fde99a.png</url>
      <title>DEV Community: Kento Nishio</title>
      <link>https://dev.to/ken2403</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ken2403"/>
    <language>en</language>
    <item>
      <title>An AI agent that triages Datadog alerts against your codebase — on Cloudflare Workers + GitHub Actions, for $0.78 per investigation</title>
      <dc:creator>Kento Nishio</dc:creator>
      <pubDate>Mon, 20 Jul 2026 15:33:12 +0000</pubDate>
      <link>https://dev.to/ken2403/an-ai-agent-that-triages-datadog-alerts-against-your-codebase-on-cloudflare-workers-github-10g1</link>
      <guid>https://dev.to/ken2403/an-ai-agent-that-triages-datadog-alerts-against-your-codebase-on-cloudflare-workers-github-10g1</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When you run a product, there is a familiar scene: a Datadog error alert lands in Slack, and someone starts a first-response investigation in the thread — "what is this error?". You read the logs, follow the trace, open the code, and form a hypothesis about the cause. The first 30 minutes are mostly routine work.&lt;/p&gt;

&lt;p&gt;This article is about an AI agent that automates those 30 minutes with Cloudflare Workers + GitHub Actions + Claude Code. It covers the architecture, why I chose this stack, and the measured cost of about a month in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built: the architecture
&lt;/h2&gt;

&lt;p&gt;There are only four components.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Datadog&lt;/td&gt;
&lt;td&gt;An error monitor fires and posts a notification to a Slack alert channel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare Workers (Hono + D1)&lt;/td&gt;
&lt;td&gt;A thin receiver that detects the alert from Slack events and launches GitHub Actions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Actions&lt;/td&gt;
&lt;td&gt;Started via &lt;code&gt;repository_dispatch&lt;/code&gt;; the environment where Claude Code (headless) runs the investigation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slack&lt;/td&gt;
&lt;td&gt;The investigation result comes back to the alert thread&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The happy path is just this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8gna8w0r58bwy8sirfd0.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%2F8gna8w0r58bwy8sirfd0.png" alt="Sequence diagram of the happy path: Datadog posts an alert to Slack, the Cloudflare Worker receives the message event and fires repository_dispatch, Claude Code investigates on GitHub Actions, and the result is posted back to the alert thread" width="799" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key decision is that &lt;strong&gt;the Worker stays a thin receiver and the heavy investigation runs on GitHub Actions&lt;/strong&gt;. Slack's Events API expects a response within 3 seconds, so the Worker only verifies the signature, classifies the message as an alert, and immediately fires &lt;code&gt;repository_dispatch&lt;/code&gt;. The investigation itself runs Claude Code CLI headless on Actions (&lt;code&gt;claude -p --model sonnet&lt;/code&gt;), working from a shallow checkout of the product repository plus the Datadog MCP server (read-only tools only).&lt;/p&gt;

&lt;p&gt;Datadog webhooks could call the Worker directly, but the flow deliberately goes through Slack. Keeping the alert, the investigation result, and the follow-up discussion in one thread turns the Slack alert channel into the archive of error handling. With the Slack post as the trigger, a threaded reply to that message is all it takes.&lt;/p&gt;

&lt;p&gt;When the investigation concludes that a code change is needed, the reply carries a Create Issue button. If we decide to act on it, the button creates a GitHub Issue, and a local Claude Code session picks up that Issue to open a fix pull request. The agent's job ends at the first-response investigation and the Issue draft; fixes go through a flow where human review applies.&lt;/p&gt;

&lt;p&gt;The agent never posts to Slack directly. It only writes the investigation result to a Markdown file, and a subsequent trusted step does the posting. Alert text can contain user input, so it is an attack surface: the payload is always handled as data, the skill directory is made read-only, and the post step refuses to post if the output contains anything that looks like a secret — a set of prompt-injection countermeasures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this stack
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The investigation can reach into your own codebase
&lt;/h3&gt;

&lt;p&gt;Datadog's built-in AI features stay within telemetry — logs, metrics, traces. What you actually want from a first response is a hypothesis down to "which file and line, and roughly how to fix it". With your own agent, you can check out the repository and have it pinpoint &lt;code&gt;file:line&lt;/code&gt; and sketch the fix. That is the biggest difference from the off-the-shelf option.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Fully usage-based, with visible per-run cost
&lt;/h3&gt;

&lt;p&gt;Every part of this agent bills only when it runs. Idle cost is zero; if no alert fires, you pay nothing. SaaS AI features tend to be tied to seats or a higher-tier SKU, and you cannot see what one alert's investigation cost. With your own build, separating API keys per feature is enough to get the measured per-run cost shown below.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Cloudflare makes the receiver cheap and simple to run
&lt;/h3&gt;

&lt;p&gt;Workers run on V8 isolates, which start fast and use little memory, so they fit the requirement of "handle a Slack event within 3 seconds and hand off to async work". For state, D1 (SQLite) is directly available from the Worker, and one dedup table was all it needed. Written with Hono, the Worker is just a small web app, and the free tier covers this workload entirely.&lt;/p&gt;

&lt;p&gt;The investigation itself lives on GitHub Actions instead of Workers because it needs to &lt;strong&gt;check out the whole repository and run Claude Code&lt;/strong&gt;. A filesystem-heavy workload that takes minutes fits an Actions job started by &lt;code&gt;repository_dispatch&lt;/code&gt; better than Workers with their CPU-time and filesystem constraints. Actions sits right next to the code, secret management is built in, and being event-driven it has no standby cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost trick: never investigate the same error twice
&lt;/h2&gt;

&lt;p&gt;This mechanism turned out to matter most in production.&lt;/p&gt;

&lt;p&gt;The reality of error alerts is that &lt;strong&gt;the same error fires again and again&lt;/strong&gt;. The errors that stay unfixed the longest are the ones with small real-world impact — they keep firing while the fix stays on the backlog. Those repeated alerts cause notification fatigue, and if an AI reruns the same investigation each time, the waste converts directly into API cost. There is no point running a full investigation on "that known error fired again".&lt;/p&gt;

&lt;p&gt;So the Worker keeps an investigated-issues table in D1, fingerprinted by the &lt;code&gt;issue.id&lt;/code&gt; that Datadog Error Tracking assigns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;incident_issues&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;issue_id&lt;/span&gt;    &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;-- Datadog Error Tracking issue.id&lt;/span&gt;
  &lt;span class="n"&gt;channel&lt;/span&gt;     &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;thread_ts&lt;/span&gt;   &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;-- first investigation thread&lt;/span&gt;
  &lt;span class="n"&gt;triaged_at&lt;/span&gt;  &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&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;If an alert with the same &lt;code&gt;issue.id&lt;/code&gt; arrives within 14 days, Actions is not launched (= zero tokens), and the new alert's thread only gets a link: "this error is already being handled — see the first investigation thread".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fywctvdb9l7p22vknu3kc.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%2Fywctvdb9l7p22vknu3kc.png" alt="Sequence diagram of the dedup path: the Worker tries to register the issue_id in D1, finds it already registered within 14 days, and replies with a link to the first investigation thread — GitHub Actions is never launched, zero tokens" width="799" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The check is a D1 &lt;code&gt;INSERT ... ON CONFLICT DO NOTHING&lt;/code&gt;, so even concurrent alerts for the same issue cannot start two investigations.&lt;/p&gt;

&lt;p&gt;A side effect of this mechanism is that &lt;strong&gt;the investigation and all follow-up discussion accumulate in the first thread&lt;/strong&gt;. Duplicate threads for the same error — "where were we discussing this again?" — structurally cannot happen. After 14 days, a recurrence is treated as "still not fixed, or the situation changed" and gets re-investigated.&lt;/p&gt;

&lt;p&gt;One deliberate design choice: when the dedup check fails (a D1 outage, or an alert without an &lt;code&gt;issue.id&lt;/code&gt;), the system &lt;strong&gt;fails toward running the investigation&lt;/strong&gt;. An occasional duplicate investigation costs less than a missed one.&lt;/p&gt;

&lt;p&gt;A few more guards keep cost and blast radius bounded:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The model is pinned to Sonnet, and the job has &lt;code&gt;timeout-minutes: 20&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The agent's allowed tools are read-only Datadog MCP tools and local file operations only (no Bash)&lt;/li&gt;
&lt;li&gt;API keys are separated per feature, so cost is measurable per feature&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Measured cost
&lt;/h2&gt;

&lt;p&gt;These are measured values from about a month in production, 2026/06/21–07/19.&lt;/p&gt;

&lt;p&gt;For context, the investigated codebase is a TypeScript-centric monorepo: roughly 4,600 source files, about 1,800 directories, over one million lines. It is a business application with users on the order of a thousand — production code at real scale, not a toy sample, running at the cost below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Alert volume vs. investigations run
&lt;/h3&gt;

&lt;p&gt;First, the denominators. Over this period, the product's error monitors fired (Triggered) &lt;strong&gt;265 times&lt;/strong&gt; in production alone. Actual investigations (GitHub Actions runs) numbered &lt;strong&gt;121&lt;/strong&gt;. Filtering out Recovered / Re-notified posts plus the dedup mechanism cut executions to less than half of the alert volume. Every skipped run costs exactly zero.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI (Anthropic API)
&lt;/h3&gt;

&lt;p&gt;The API key dedicated to this feature consumed &lt;strong&gt;$93.90&lt;/strong&gt; (including test runs during initial setup). Divided by 121 investigations, that is &lt;strong&gt;about $0.78 per investigation&lt;/strong&gt;. Against a human spending 30 minutes on the same first response, under $1 per run is easily worth it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it actually cheaper than Datadog's built-in option?
&lt;/h3&gt;

&lt;p&gt;Datadog offers autonomous investigation via &lt;a href="https://www.datadoghq.com/product/ai/bits-investigation/" rel="noopener noreferrer"&gt;Bits Investigation&lt;/a&gt;, billed through &lt;a href="https://www.datadoghq.com/pricing/?product=bits-ai-sre" rel="noopener noreferrer"&gt;AI Credits&lt;/a&gt;. At the time of writing: &lt;strong&gt;$500 per 500 credits per month (annual; on-demand is $1.30/credit)&lt;/strong&gt;, and one autonomous investigation consumes &lt;strong&gt;about 6.5 credits&lt;/strong&gt; — roughly &lt;strong&gt;$6.5–8.5 per investigation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At $0.78 per run, the self-built agent costs &lt;strong&gt;1/8 to 1/10&lt;/strong&gt; of that; 121 runs a month through Bits would come to roughly $790–1,000. The credit commitment also starts at $500/month, while this stack has zero fixed cost. Bits is managed and needs no setup, so it is not a strict win-lose comparison — but for the specific job of alert-driven first-response investigation, self-built runs an order of magnitude cheaper.&lt;/p&gt;

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

&lt;p&gt;The &lt;code&gt;ubuntu-latest&lt;/code&gt; jobs averaged 3 minutes each; 121 runs totaled about 360 minutes for the month. That fits inside the Actions free allowance included in GitHub's paid plans (3,000 min/month on Team, 50,000 on Enterprise), so &lt;strong&gt;the added cost was zero&lt;/strong&gt;. Even if the allowance were exhausted and every minute were billed, Linux runners cost $0.008/min — under $3/month. With &lt;code&gt;timeout-minutes: 20&lt;/code&gt; on each job, the worst case is 2,420 minutes ≈ $19.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloudflare
&lt;/h3&gt;

&lt;p&gt;The Worker only receives alert events, so request volume never approaches the free tier (100k requests/day). D1 reads and writes a few rows per alert — a rounding error against its free tier. Effectively &lt;strong&gt;$0&lt;/strong&gt; (or just the $5/month base fee if you are on Workers Paid anyway).&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Measured (~1 month)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic API (the investigations, 121 runs)&lt;/td&gt;
&lt;td&gt;$93.90 (≈ $0.78/run)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Actions&lt;/td&gt;
&lt;td&gt;$0 (within free allowance)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare Workers + D1&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;≈ $94&lt;/strong&gt; (reference: ≈ $790–1,000 in Bits Investigation terms)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For $94 a month, every alert gets an automatic first-response investigation, and the results accumulate in threads. Against the cost of a human doing that first response every time, it pays for itself. And since nearly the entire bill is Anthropic API usage — pure investigation cost — the stack carries no infrastructure fixed cost at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;The first-response agent for Datadog alerts is composed of Cloudflare Workers (receiver) + GitHub Actions (execution) + Claude Code (the investigation). The essence of the stack is that &lt;strong&gt;it is built only from event-driven parts, so fixed cost structurally cannot occur&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In operation, the mechanism that mattered more than any investigative capability was the plain one: never investigate the same error twice. Error-alert operations live with the reality that low-impact, unfixed errors keep firing, and naively wiring an AI agent to alerts converts that reality directly into API spend. Dedup turned out to be a cost control, a notification-fatigue control, and an operational improvement that concentrates discussion into one thread, all at once.&lt;/p&gt;

&lt;p&gt;As a next step, I want to extend past the first response: first, having the Actions-side agent go as far as cutting a fix branch and opening a draft PR (merge decisions stay with humans). Beyond that, since Cloudflare keeps expanding agent-oriented runtimes such as Sandboxes and Dynamic Workers, I want to try moving the investigation and fix execution from GitHub Actions onto Cloudflare, completing everything from the receiver to the fix PR at the edge.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.cloudflare.com/workers/" rel="noopener noreferrer"&gt;Cloudflare Workers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.cloudflare.com/d1/" rel="noopener noreferrer"&gt;Cloudflare D1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.datadoghq.com/bits_ai/mcp_server/" rel="noopener noreferrer"&gt;Datadog MCP Server&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>cloudflare</category>
      <category>githubactions</category>
      <category>claude</category>
    </item>
  </channel>
</rss>
