<?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: Chikoku_NEKO</title>
    <description>The latest articles on DEV Community by Chikoku_NEKO (@chikoku_neko).</description>
    <link>https://dev.to/chikoku_neko</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%2F4073554%2Fcf9a60a7-d850-4ae0-b517-2ef21875829f.png</url>
      <title>DEV Community: Chikoku_NEKO</title>
      <link>https://dev.to/chikoku_neko</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chikoku_neko"/>
    <language>en</language>
    <item>
      <title>Silent Failures in Unattended AI Agents — How I Built a Postmortem Tool to Find Them</title>
      <dc:creator>Chikoku_NEKO</dc:creator>
      <pubDate>Tue, 11 Aug 2026 17:43:46 +0000</pubDate>
      <link>https://dev.to/chikoku_neko/silent-failures-in-unattended-ai-agents-how-i-built-a-postmortem-tool-to-find-them-29ci</link>
      <guid>https://dev.to/chikoku_neko/silent-failures-in-unattended-ai-agents-how-i-built-a-postmortem-tool-to-find-them-29ci</guid>
      <description>&lt;p&gt;A cron-scheduled agent of mine ran fine: exit code 0, no stderr. But the weekly report it was supposed to write never appeared, and nothing told me. I only noticed days later, checking the directory by hand.&lt;/p&gt;

&lt;p&gt;I call this a silent failure, and I think it's the nastiest failure mode of unattended agents: the job reports success, so no alerting ever fires, and the only detection mechanism left is a human happening to look. I built agent-coroner to catch these automatically. v0.1.0 is now on GitHub, PyPI, and the Claude Code plugin marketplace.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;For five weeks straight, my weekly-report job ran without a problem. It pulled data, generated markdown, wrote a file every Friday morning. I didn't monitor it actively, because it was boring infrastructure that appeared to be working.&lt;/p&gt;

&lt;p&gt;Week six: no report. The logs showed nothing obviously wrong, and the job had no alerting of its own. By week seven it had recovered on its own and everything looked normal again. To this day I haven't root-caused that one missing week. That's the part that bothered me most: without an artifact check, I wouldn't even have known it happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  The design
&lt;/h2&gt;

&lt;p&gt;Instead of trying to guess when things go wrong, I decided to be explicit about what each job should produce, then check those promises deterministically. There are three layers.&lt;/p&gt;

&lt;p&gt;The first layer is a contracts file, where you declare what each job promises:&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;weekly-report&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FRI&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;08:30"&lt;/span&gt;
    &lt;span class="na"&gt;grace_minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
    &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reports/weekly-*.md"&lt;/span&gt;
        &lt;span class="na"&gt;max_age_hours&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;192&lt;/span&gt;
        &lt;span class="na"&gt;min_bytes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;
    &lt;span class="na"&gt;logs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run.log"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second layer is the checker, a small CLI you run from cron or Task Scheduler. It asks four questions: does the file exist, is it fresh (&lt;code&gt;max_age_hours&lt;/code&gt;), is it big enough (&lt;code&gt;min_bytes&lt;/code&gt;), and did your optional verify command pass. No LLM is involved, so a run takes seconds and ongoing monitoring costs nothing. It exits non-zero if a contract is violated and zero otherwise, so you can wire it into any alerting you already have.&lt;/p&gt;

&lt;p&gt;The third layer is the autopsy. Only when a contract breaks, Claude is dispatched read-only (&lt;code&gt;--allowedTools "Read,Grep,Glob"&lt;/code&gt;) to read the logs and write a four-section postmortem: facts, hypotheses ranked by confidence with log quotes, verification steps for a human to run, and prevention suggestions. It diagnoses; it never modifies anything.&lt;/p&gt;

&lt;p&gt;One design decision worth spelling out: detection never depends on the LLM. The checker always runs first, and if the autopsy crashes, the violation notification still goes out. There's also no self-healing on purpose. Once you let an LLM patch things in an unattended system, failures start getting hidden instead of reported, and I trust a monitor that only reports.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dogfooding it on a new project
&lt;/h2&gt;

&lt;p&gt;The day after publishing, I started a new project (a paper-trading eval that forecasts day-ahead prices on JEPX, Japan's electricity exchange) and registered its weekly accuracy report as a coroner contract. Just building and registering that pipeline surfaced two traps of exactly the kind the tool exists for.&lt;/p&gt;

&lt;p&gt;The first trap: the JEPX CSV endpoint returns HTTP 200 with a zero-byte body when the &lt;code&gt;Referer&lt;/code&gt; header is missing. Success code, no data. I caught it with curl while writing the fetch layer. A status-code-only check would have "succeeded", saved an empty file, and quietly poisoned everything downstream. The defense is now two layers deep: the fetcher rejects empty bodies outright, and the contract's &lt;code&gt;min_bytes&lt;/code&gt; rule guards the artifact. If one goes quiet, the other still fires.&lt;/p&gt;

&lt;p&gt;The second trap: my monitored artifacts live in a different repository on a different drive, so I wrote the contract with an absolute-path glob, and the checker crashed. Glob resolution is delegated to pathlib's relative globbing, so absolute paths don't work. The fix is to set &lt;code&gt;workdir&lt;/code&gt; (which defaults to the directory containing &lt;code&gt;contracts.yaml&lt;/code&gt;) and use a relative glob. After rewriting it, the checker passed: &lt;code&gt;power-eval-weekly: ok&lt;/code&gt;, exit 0.&lt;/p&gt;

&lt;p&gt;I found the second one funny in hindsight. Putting the verification tool into real service exposed a gap in the verification tool's own docs, so the verifier got verified. The README now documents &lt;code&gt;workdir&lt;/code&gt; and the relative-glob rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick start
&lt;/h2&gt;

&lt;p&gt;Install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv tool &lt;span class="nb"&gt;install &lt;/span&gt;agent-coroner   &lt;span class="c"&gt;# or: pip install agent-coroner&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;claude&lt;/code&gt; CLI on PATH is required only for autopsies; the checker works standalone.&lt;/p&gt;

&lt;p&gt;Write &lt;code&gt;contracts.yaml&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="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;weekly-report&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FRI&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;08:30"&lt;/span&gt;
    &lt;span class="na"&gt;grace_minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
    &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reports/weekly-*.md"&lt;/span&gt;
        &lt;span class="na"&gt;max_age_hours&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;192&lt;/span&gt;
        &lt;span class="na"&gt;min_bytes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;
    &lt;span class="na"&gt;logs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run.log"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the checker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;coroner check &lt;span class="nt"&gt;--config&lt;/span&gt; contracts.yaml &lt;span class="nt"&gt;--no-autopsy&lt;/span&gt;
&lt;span class="go"&gt;[coroner] 1 violation detected
&lt;/span&gt;&lt;span class="gp"&gt;- weekly-report / reports/weekly-*.md: missing (no file matches 'reports/weekly-*.md' under &amp;lt;contracts-dir&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;span class="go"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once &lt;code&gt;reports/weekly-*.md&lt;/code&gt; exists, is fresh, and meets &lt;code&gt;min_bytes&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;coroner check &lt;span class="nt"&gt;--config&lt;/span&gt; contracts.yaml &lt;span class="nt"&gt;--no-autopsy&lt;/span&gt;
&lt;span class="go"&gt;weekly-report: ok
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;span class="go"&gt;0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register it on a schedule. On Linux/macOS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*/15 * * * * coroner check --config /path/to/contracts.yaml &amp;gt;&amp;gt; /var/log/coroner.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;schtasks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/tn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"coroner-check"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/tr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"coroner check --config C:\path\to\contracts.yaml"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/sc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;daily&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/st&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;09:00&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notifications are delegated to an external command of your choice (email, Slack, whatever), with &lt;code&gt;{message_file}&lt;/code&gt; holding the path to the full message text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Claude Code integration
&lt;/h2&gt;

&lt;p&gt;If you use Claude Code, there's also a plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/plugin marketplace add Chikoku-NEKO/agent-coroner
/plugin install agent-coroner@agent-coroner-marketplace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It adds &lt;code&gt;/coroner-status&lt;/code&gt; (which jobs are ok, which have violations), &lt;code&gt;/autopsy &amp;lt;job-name&amp;gt;&lt;/code&gt; (run a postmortem on demand), and a SessionStart hook that surfaces unread postmortems when you open a session.&lt;/p&gt;

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

&lt;p&gt;If you run anything unattended that's supposed to produce an artifact — weekly reports, data pipelines, scheduled Claude jobs — an existence-and-freshness check on the artifact is cheap insurance, whether or not you use this tool. agent-coroner just makes it declarative: one YAML file, a deterministic checker, and a read-only postmortem when something breaks.&lt;/p&gt;

&lt;p&gt;Links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/Chikoku-NEKO/agent-coroner" rel="noopener noreferrer"&gt;https://github.com/Chikoku-NEKO/agent-coroner&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PyPI: &lt;a href="https://pypi.org/project/agent-coroner/" rel="noopener noreferrer"&gt;https://pypi.org/project/agent-coroner/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Claude Code marketplace: &lt;code&gt;agent-coroner&lt;/code&gt; plugin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you try it and something breaks during registration, file an issue. That's how the two traps above got documented.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
