<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Claude on hippotion</title><link>https://blog.hippotion.com/tags/claude/</link><description>Recent content in Claude on hippotion</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Fri, 29 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.hippotion.com/tags/claude/index.xml" rel="self" type="application/rss+xml"/><item><title>Every Robot in My House Can Text Me Now</title><link>https://blog.hippotion.com/posts/every-robot-texts-me/</link><pubDate>Fri, 29 May 2026 00:00:00 +0000</pubDate><guid>https://blog.hippotion.com/posts/every-robot-texts-me/</guid><description>My house is full of automation that never told me anything — until I gave it one push bus. The first thing I taught it to do was warn me before Claude Code cuts out mid-task.</description><content:encoded><![CDATA[<h2 id="the-silence">The silence</h2>
<p>My house runs on quiet little robots. A tracker watches my kombucha ferment. A
job narrates kids&rsquo; books in Hungarian. A media stack pulls and files things. Home
Assistant minds the sensors. A dozen services, all doing their jobs, all
completely mute. When a batch finished or an import failed, I found out the same
way every time: by going to look.</p>
<p>Then the silence got expensive. Claude Code stopped dead in the middle of a task
because I&rsquo;d burned through my plan&rsquo;s usage window — no warning, no countdown,
just a wall. The information <em>existed</em>; a dashboard in my own cluster was already
polling it. It just had no way to reach my pocket.</p>
<p>So I built one thing: a push bus. One place anything in the cluster can POST to,
that actually buzzes my phone. And the first job I gave it was to warn me before
my AI assistant goes dark.</p>
<hr>
<h2 id="the-boring-part-said-honestly">The boring part (said honestly)</h2>
<p>The bus is <a href="https://ntfy.sh">ntfy</a> — a self-hosted pub/sub notifier. Picking it
took about five minutes, because self-hosting ntfy for a homelab is a thoroughly
solved problem. There are at least three off-the-shelf bridges from Prometheus
Alertmanager to ntfy. I&rsquo;m not going to pretend the bus is the clever bit.</p>
<p>What I <em>did</em> do deliberately:</p>
<ul>
<li>📦 Deployed it <strong>GitOps-native</strong> — one entry in my app-of-apps, reconciled by
Argo CD, no <code>docker run</code> anywhere.</li>
<li>🔒 Locked it to <strong>deny-all auth</strong> with bearer tokens. Security alerts ride this
bus; a world-readable topic on a public URL was a non-starter. (Which also means
it sits <em>outside</em> my usual OAuth gate — the phone app can&rsquo;t do an interactive
login flow, so ntfy does its own token auth.)</li>
<li>🏷️ Topics by severity: <code>hl-crit</code>, <code>hl-warn</code>, <code>hl-info</code>, <code>hl-event</code>. Subscribe
and mute by how much I care.</li>
</ul>
<p>Then the interesting parts showed up at the edges, where they always do.</p>
<hr>
<h2 id="edge-one-my-own-firewall-403d-me">Edge one: my own firewall 403&rsquo;d me</h2>
<p>First test, the usage producer POSTing to <code>https://ntfy.hippotion.com</code>:</p>
<pre tabindex="0"><code>HTTP 403 Forbidden
error code: 1010
</code></pre><p>That <code>1010</code> looks like ntfy rejecting my token. It isn&rsquo;t. <strong>It&rsquo;s Cloudflare.</strong>
Error 1010 means &ldquo;your browser signature is banned&rdquo; — Cloudflare&rsquo;s bot protection
took one look at a Python script&rsquo;s <code>urllib</code> User-Agent and slammed the door.</p>
<p>My own producer couldn&rsquo;t reach my own bus, because the request left the cluster,
went all the way out to my own edge, and got flagged as a bot on the way back in.</p>
<p>The fix is the architecture I should&rsquo;ve had from the start: in-cluster producers
POST to the <strong>internal</strong> service address and never touch the public internet at
all.</p>
<pre tabindex="0"><code># wrong: out to Cloudflare and back, gets bot-blocked
https://ntfy.hippotion.com/hl-warn

# right: stays inside the cluster
http://ntfy.web-ntfy.svc.cluster.local/hl-warn
</code></pre><p>The phone still uses the public URL happily — the real ntfy app carries a
signature Cloudflare trusts. Only scripts trip 1010. <strong>Lesson: your own edge is
not your friend when you&rsquo;re a script. Keep cluster traffic in the cluster.</strong></p>
<hr>
<h2 id="edge-two-the-obvious-data-source-was-lying">Edge two: the obvious data source was lying</h2>
<p>To warn me about Claude usage, the naïve move is to parse Claude Code&rsquo;s local
logs — they sit right there in <code>~/.claude/projects/.../*.jsonl</code>, token counts and
all.</p>
<p>Don&rsquo;t. Those counts are <strong>unreliable for accounting</strong> — known to undercount,
wildly, in some cases by ~100x. Every tool that parses that JSONL inherits the
bug.</p>
<p>The number that&rsquo;s actually true lives in the claude.ai usage API — the same
<code>five_hour</code> and <code>seven_day</code> windows your plan enforces against. And I already had
a service polling exactly that. So the producer is just a tiny sidecar on that
existing pod, reading its <code>/api/usage</code> over <strong>localhost</strong> (same pod — no network
policy to negotiate, no second credential, nothing else hammering claude.ai):</p>
<ul>
<li>📈 ≥80% of a window → <code>hl-warn</code> (high).</li>
<li>🚨 ≥95% → <code>hl-crit</code> (urgent).</li>
<li>🔁 One ping per window per reset cycle, escalating warn→crit, keyed on the
reset timestamp so it never spams.</li>
</ul>
<p>The first time it mattered, my phone buzzed at 80% with hours of runway left
instead of a brick wall mid-task.</p>
<hr>
<h2 id="what-id-tell-past-me">What I&rsquo;d tell past me</h2>
<p>Three things, none of them about ntfy:</p>
<ol>
<li><strong>Reuse the signal you already have.</strong> I didn&rsquo;t build a usage poller — I bolted
a sidecar onto the one already running. The smallest producer is one that reads
localhost.</li>
<li><strong>Your own edge can betray you.</strong> A firewall that protects you from bots will
happily block your own automation. In-cluster talks in-cluster.</li>
<li><strong>Check whether your data source is telling the truth</strong> before you build an
alert on it. An alert you don&rsquo;t trust is worse than no alert — you&rsquo;ll learn to
ignore it, and then it&rsquo;ll be right once.</li>
</ol>
<p>Next, the high-leverage move: point Prometheus Alertmanager at the same bus, and
every infra alert I have — plus every one I&rsquo;ll ever add — lands on the phone
through one bridge. The kombucha ping can wait. The disk-full one can&rsquo;t.</p>
<p>The house is still full of quiet robots. The difference is now they know my
number.</p>
]]></content:encoded></item><item><title>🚩 I Built a Usage Dashboard and Tripped Claude Fable 5's Safety Net</title><link>https://blog.hippotion.com/posts/when-claude-flagged-my-own-dashboard/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://blog.hippotion.com/posts/when-claude-flagged-my-own-dashboard/</guid><description>I asked Claude Fable 5 to help me self-host a dashboard for my own Claude usage. Halfway through, its dual-use safety measures flagged the conversation and downshifted me to Opus 4.8. Nothing I did was wrong — the request just had the shape of something that is. That gap, between what a thing looks like and what it&amp;rsquo;s for, turns out to be the whole story.</description><content:encoded><![CDATA[<h2 id="the-thing-i-was-actually-building">The thing I was actually building</h2>
<p>I wanted a small web page on my homelab that shows my Claude usage — the 5-hour
session window, the weekly limits, the per-model split. There&rsquo;s a nice Electron
widget out there that does this on the desktop, but I don&rsquo;t want a desktop app; I
want a URL behind my own OAuth that I can glance at from my phone.</p>
<p>The mechanics are unremarkable. The claude.ai web app reads those numbers from a
couple of undocumented endpoints using your logged-in session cookie. So a
self-hosted version does the same thing server-side: hold the session token as a
secret, replay the same calls, cache the result, render some bars. An afternoon&rsquo;s
work. I was pairing with <strong>Claude Fable 5</strong> on it — Anthropic&rsquo;s newest model, and
the one that ships with extra safety measures around dual-use capability.</p>
<p>Then, partway through, I got the message: <em>Fable 5 flagged something in this
session and switched to a more conservative model.</em> It dropped me to <strong>Opus 4.8</strong>
for the rest of the conversation. Safe conversations sometimes trip it, the notice
said. Send feedback.</p>
<h2 id="i-wasnt-doing-anything-wrong-thats-the-interesting-part">I wasn&rsquo;t doing anything wrong. That&rsquo;s the interesting part.</h2>
<p>My first reaction was the obvious one — <em>what did I say?</em> But I knew exactly what
I&rsquo;d built, and none of it was sketchy. It was my account, my usage data, my
hardware, my OAuth in front of it.</p>
<p>So I went looking at the request the way a classifier would — not &ldquo;what did he
mean&rdquo; but &ldquo;what does this look like.&rdquo; And from that angle it&rsquo;s a different
picture entirely. Stack up the surface features:</p>
<ul>
<li>🔑 capturing a <strong>session token</strong> and storing it to replay later</li>
<li>🌐 sending it to an <strong>undocumented API</strong> that isn&rsquo;t meant for third parties</li>
<li>🕵️ spoofing a <strong>browser User-Agent</strong> so the request blends in</li>
<li>🧱 detecting and working around a <strong>Cloudflare bot challenge</strong></li>
</ul>
<p>Read that list cold, with no context. That&rsquo;s not a usage dashboard. That&rsquo;s the
exact signature of credential theft and scraping tooling. Every individual move
is one a malicious script would also make. The only thing separating my afternoon
project from the bad version is <em>whose</em> account it touches and <em>why</em> — and intent
is precisely the part that doesn&rsquo;t show up in the tokens.</p>
<h2 id="surface-vs-intent">Surface vs. intent</h2>
<p>This is the part worth sitting with, because it&rsquo;s not a Claude quirk — it&rsquo;s the
shape of every content classifier, every WAF rule, every fraud model I&rsquo;ve ever
run in production.</p>
<p>A detector scores what it can see. It cannot see intent; it sees features. And
the features of &ldquo;monitor my own usage&rdquo; and &ldquo;harvest someone else&rsquo;s session&rdquo;
overlap almost completely, because the <em>technique</em> is identical — the difference
lives entirely in context the model has been deliberately built not to over-trust.
You can&rsquo;t tune that gap away. You can only pick where to sit on the
precision/recall curve, and Fable 5 — being the high-capability model with the
extra dual-use measures bolted on — sits where it catches the pattern even when it
costs some false positives, then hands off to Opus 4.8. I was the false positive.
The system did roughly the right thing for roughly the right reason; it just
doesn&rsquo;t feel that way when it&rsquo;s pointed at you.</p>
<p>The honest engineering takeaway is the one I keep relearning: <strong>if a benign task
has the silhouette of an abusive one, expect to get treated like the silhouette.</strong>
Not just by AI — by rate limiters, by bot detection, by the fraud team. The fix
isn&rsquo;t to be offended. It&rsquo;s to recognize the silhouette, and where it matters,
make the legitimate context legible up front.</p>
<h2 id="what-id-do-differently">What I&rsquo;d do differently</h2>
<p>Practically, very little — the project was fine, and it downshifted to a model
that finished the job. But the framing changed how I built it. I leaned harder
into the parts that make intent <em>visible in the design</em>: the session token never
leaves the server, it lives in Vault and arrives as an injected secret, the whole
thing sits behind OAuth, and it polls on a leash instead of hammering. Not because
a classifier made me, but because those are the same choices that make it
obviously a personal dashboard and not a harvesting bot — to a reviewer, to
future-me, and yes, to a model reading over my shoulder.</p>
<p>The widget rides your credential on your desktop. Mine keeps it server-side behind
my own front door. Turns out building it the trustworthy way and building it the
<em>legibly</em> trustworthy way are the same work — and getting flagged is what made me
notice the difference.</p>
]]></content:encoded></item></channel></rss>