<?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: Sungwoo Lee</title>
    <description>The latest articles on DEV Community by Sungwoo Lee (@sungwoo_lee_e0f26be4a29fd).</description>
    <link>https://dev.to/sungwoo_lee_e0f26be4a29fd</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%2F3989473%2F81eb30d6-b8ed-42ec-b5fb-a2edc4305bfe.png</url>
      <title>DEV Community: Sungwoo Lee</title>
      <link>https://dev.to/sungwoo_lee_e0f26be4a29fd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sungwoo_lee_e0f26be4a29fd"/>
    <language>en</language>
    <item>
      <title>Playwright connect_over_cdp Hangs 100s? Check for One Dead Tab</title>
      <dc:creator>Sungwoo Lee</dc:creator>
      <pubDate>Sat, 07 Nov 2026 01:06:38 +0000</pubDate>
      <link>https://dev.to/sungwoo_lee_e0f26be4a29fd/playwright-connectovercdp-hangs-100s-check-for-one-dead-tab-4ikc</link>
      <guid>https://dev.to/sungwoo_lee_e0f26be4a29fd/playwright-connectovercdp-hangs-100s-check-for-one-dead-tab-4ikc</guid>
      <description>&lt;p&gt;If &lt;code&gt;connect_over_cdp&lt;/code&gt; hangs for 100 seconds while &lt;code&gt;/json/version&lt;/code&gt; answers instantly, Chrome is not dead. One open tab has a hung renderer, and Playwright's auto-attach is waiting on it. Find that tab over a raw browser-level websocket, close only that tab, and connect again. Do not restart Chrome.&lt;/p&gt;

&lt;p&gt;I got this wrong the first time. The write-up of the original incident is in &lt;a href="https://my-blog.org/claude-code/post/cdp-connect-hang-one-tab?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=cdp-connect-hang-one-tab" rel="noopener noreferrer"&gt;this post on my blog&lt;/a&gt;; this is the version for people who just want the diagnosis and the commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptom: a 100-second hang with a healthy endpoint
&lt;/h2&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%2Fbvhbl10asu26hqsn5goq.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%2Fbvhbl10asu26hqsn5goq.png" alt="connect_over_cdp waited 100 seconds, while a per-page probe should be capped at about 5 seconds" width="799" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Chrome was running with its debugging port on 9401. My script did the usual thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;playwright.sync_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sync_playwright&lt;/span&gt;

&lt;span class="n"&gt;CDP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://127.0.0.1:9401&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;sync_playwright&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect_over_cdp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CDP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# sits here for 100 seconds, then times out
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The call did not fail fast. It stalled until the 100-second timeout. Meanwhile this returned immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; http://127.0.0.1:9401/json/version
&lt;span class="c"&gt;# 200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That combination is the whole puzzle: the HTTP side of the debugging port is fine, and the Playwright handshake is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "Chrome is dead" was the wrong diagnosis
&lt;/h2&gt;

&lt;p&gt;My first conclusion was that Chrome had died and needed a restart. That does not fit the evidence. A &lt;code&gt;200&lt;/code&gt; from &lt;code&gt;/json/version&lt;/code&gt; means the browser process is up and its DevTools server is serving requests. A dead browser does not answer.&lt;/p&gt;

&lt;p&gt;So the question changes from "is the browser alive?" to "what is Playwright waiting for?"&lt;/p&gt;

&lt;h2&gt;
  
  
  The real cause: auto-attach to a tab that cannot answer
&lt;/h2&gt;

&lt;p&gt;When &lt;code&gt;connect_over_cdp&lt;/code&gt; connects, Playwright automatically attaches to every page that is already open. If one of those pages has a hung renderer, the attach step for that page never completes, and the whole connection is dragged along until the timeout.&lt;/p&gt;

&lt;p&gt;So one bad tab, out of however many you have open, is enough to make the entire connect call look like a browser failure. The browser is fine. The tab is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Telling browser failure from tab failure
&lt;/h2&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%2F4vlrg9w1u3zo9bvzub0g.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%2F4vlrg9w1u3zo9bvzub0g.png" alt="With one hung tab, /json/version returns 200 and Target.getTargets answers but one page does not answer Runtime.evaluate; if the browser is down nothing answers" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check three layers, from the outside in:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Probe&lt;/th&gt;
&lt;th&gt;What a healthy answer means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HTTP endpoint&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/json/version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Browser process and CDP server are up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser target&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Target.getTargets&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The browser itself responds over the websocket&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Individual page&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Runtime.evaluate&lt;/code&gt; with a trivial expression&lt;/td&gt;
&lt;td&gt;That page's renderer is responsive&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If the first two answer and one page does not answer the third, the browser is not the problem. That tab is.&lt;/p&gt;

&lt;p&gt;The trick is to run these probes without going through Playwright, because Playwright's own connect is the thing that hangs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding and closing the dead tab
&lt;/h2&gt;

&lt;p&gt;Connect straight to the browser websocket URL that &lt;code&gt;/json/version&lt;/code&gt; reports, list the page targets, and probe each one with a short timeout. Three to five seconds per page is plenty; the sketch below uses five. This sketch uses the &lt;code&gt;websocket-client&lt;/code&gt; package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;

&lt;span class="n"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9401&lt;/span&gt;
&lt;span class="n"&gt;ver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://127.0.0.1:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/json/version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ver&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;webSocketDebuggerUrl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt;
    &lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;method&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;params&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sessionId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;
    &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# raises WebSocketTimeoutException if nothing answers in time
&lt;/span&gt;        &lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;

&lt;span class="n"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Target.getTargets&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;targetInfos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;page&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Target.attachToTarget&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;targetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;targetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flatten&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;})[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sessionId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Runtime.evaluate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expression&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1+1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Target.detachFromTarget&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sessionId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;targetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebSocketTimeoutException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HUNG&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;targetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the code is syntax-checked but I have not re-run it against a live hung tab for this write-up. Whatever line prints &lt;code&gt;HUNG&lt;/code&gt; gives you the &lt;code&gt;targetId&lt;/code&gt; to write down. Then close just that tab on the same browser connection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Target.closeTarget&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;targetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;the id you wrote down&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now call &lt;code&gt;connect_over_cdp&lt;/code&gt; again. With the dead tab gone there is nothing for auto-attach to choke on, and the connect returns normally.&lt;/p&gt;

&lt;p&gt;If the connect works but you want a clean slate, &lt;code&gt;pg.goto('about:blank')&lt;/code&gt; on a page and &lt;code&gt;pg.close()&lt;/code&gt; are the Playwright-level equivalents once you are attached.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just restart Chrome
&lt;/h2&gt;

&lt;p&gt;Restarting looks simpler, but if several jobs share one Chrome profile, a restart wipes the logged-in sessions and in-progress state of all the other jobs, to fix a problem that lived in one tab. Closing a single target costs nothing to the others.&lt;/p&gt;

&lt;p&gt;If you really do need to shut the browser down, ask it to leave politely with the CDP &lt;code&gt;Browser.close&lt;/code&gt; command, for example &lt;code&gt;s.send("Browser.close")&lt;/code&gt; on a CDP session, rather than killing it by process name. Killing by name hits every Chrome on the machine, including ones you did not mean to touch.&lt;/p&gt;

&lt;h2&gt;
  
  
  When this diagnosis does not apply
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/json/version&lt;/code&gt; does not respond at all.&lt;/strong&gt; Then the browser or its debugging server really is down, and a restart is the right move.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You are closing the last tab.&lt;/strong&gt; Chrome exits when its last tab closes. Open a blank tab first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The hung tab is doing real work.&lt;/strong&gt; Save whatever result it has before you close it, because closing discards it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A short pre-connect checklist
&lt;/h2&gt;

&lt;p&gt;Before pointing Playwright at a long-lived Chrome:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run the &lt;code&gt;curl&lt;/code&gt; check and confirm you get &lt;code&gt;200&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If connect still stalls, stop waiting and probe &lt;code&gt;Target.getTargets&lt;/code&gt; over the raw websocket.&lt;/li&gt;
&lt;li&gt;Probe each page with a trivial &lt;code&gt;Runtime.evaluate&lt;/code&gt; and a 3 to 5 second cap.&lt;/li&gt;
&lt;li&gt;Close only the page that does not answer.&lt;/li&gt;
&lt;li&gt;Reconnect.&lt;/li&gt;
&lt;li&gt;Restart the browser only if step 1 fails.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The longer walk-through, including the misdiagnosis and the FAQ, is in the original post: &lt;a href="https://my-blog.org/claude-code/post/cdp-connect-hang-one-tab?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=cdp-connect-hang-one-tab" rel="noopener noreferrer"&gt;Claude Code CDP connect hang, one tab at fault&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Free tools I keep on my own site: &lt;a href="https://my-blog.org/tools?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=cdp-connect-hang-one-tab" rel="noopener noreferrer"&gt;34 calculators and how-to guides&lt;/a&gt; - loans, severance pay, take-home salary, savings vs deposits. New ones go out in one email, no ads.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>chrome</category>
      <category>debugging</category>
      <category>automation</category>
    </item>
    <item>
      <title>Claude Code Auto-Compact: 12 of 22 Drops Were Real, Half Were One Turn</title>
      <dc:creator>Sungwoo Lee</dc:creator>
      <pubDate>Tue, 27 Oct 2026 23:33:33 +0000</pubDate>
      <link>https://dev.to/sungwoo_lee_e0f26be4a29fd/claude-code-auto-compact-12-of-22-drops-were-real-half-were-one-turn-1aab</link>
      <guid>https://dev.to/sungwoo_lee_e0f26be4a29fd/claude-code-auto-compact-12-of-22-drops-were-real-half-were-one-turn-1aab</guid>
      <description>&lt;p&gt;If Claude Code keeps summarizing your session, the usual explanation is "the conversation got long." In our transcripts that was only half the story. We re-judged 22 sudden context drops across two projects (2026-08-09). Only 12 were real auto-compactions, and half of those 12 came from one large input landing in a single turn, not from slow accumulation. All six of those surge cases were turns where we had called the advisor, a stronger model consulted with the full conversation.&lt;/p&gt;

&lt;p&gt;That changes the fix. Raising the context cap helps accumulation. It does nothing for a single turn that is big enough to cross the line again. Below is how we told the cases apart, what the trigger point turned out to be, and what we do first now. The full write-up, including the parts we got wrong twice, is in &lt;a href="https://my-blog.org/claude-code/post/claude-code-autocompact-single-turn?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=claude-code-autocompact-single-turn" rel="noopener noreferrer"&gt;the original post on my-blog.org&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What counts as a real compaction
&lt;/h2&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%2Fjcgxsq7t86r18wvtwfbc.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%2Fjcgxsq7t86r18wvtwfbc.png" alt="Of 22 context drops, 12 were real compactions, 7 were resume artifacts, and 3 were advisor round-trips" width="800" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Auto-compact summarizes the conversation when context reaches a policy limit. It works in two stages: it first discards old tool output, and only if that is not enough does it summarize the conversation itself. That order matters later, because it means big tool output is exactly what gets thrown away first.&lt;/p&gt;

&lt;p&gt;Of our 22 drops, 12 were real compactions, 3 were advisor round-trips that bounced back, and 7 were artifacts of session resume. If you count every dip in a context graph as a compaction, you will blame the wrong thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not classify by ratio
&lt;/h2&gt;

&lt;p&gt;Our first attempt was a ratio. A compaction rewrites the prefix, so we expected &lt;code&gt;cache_write / context-after&lt;/code&gt; to be close to 1. It wasn't. Across real compactions that ratio swung from 0.58 to 1.00.&lt;/p&gt;

&lt;p&gt;The reason is the denominator. During back-to-back compactions the system prompt and tool schemas are cache hits, so the ratio drops to 0.6 to 0.75. On the first compaction after a long gap everything is rewritten and the ratio approaches 1.00. That cached portion was about 31.5k in both projects (116,310 minus 84,459 is 31,851; 92,999 minus 61,454 is 31,545). The ratio was measuring cache state, not whether a compaction happened.&lt;/p&gt;

&lt;p&gt;An absolute &lt;code&gt;cache_write&lt;/code&gt; value separates the cases cleanly:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Real compaction&lt;/td&gt;
&lt;td&gt;cache_write of roughly 50,000 or more&lt;/td&gt;
&lt;td&gt;The conversation was actually summarized&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Advisor round-trip&lt;/td&gt;
&lt;td&gt;cache_write of roughly 10,000 or less&lt;/td&gt;
&lt;td&gt;Context swelled briefly and came back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resume artifact&lt;/td&gt;
&lt;td&gt;cache_write 0 and context-after 0&lt;/td&gt;
&lt;td&gt;A recording artifact of resuming a session&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The classification script
&lt;/h2&gt;

&lt;p&gt;Transcripts record the same request on two or three rows. Deduplicate by &lt;code&gt;requestId&lt;/code&gt; first, or usage is overcounted by about 1.9x. Then compute per-turn context as &lt;code&gt;input + cache_read + cache_creation&lt;/code&gt;, find the adjacent-turn drops, and classify. The order of the checks matters, so resume artifacts are not mislabeled as advisor round-trips.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ctx_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# field names depend on your transcript schema
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cache_read&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cache_creation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx_after&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cw&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;ctx_after&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resume artifact&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cw&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;50_000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;real compaction&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cw&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;advisor round-trip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;undecided&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;transcript_rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="c1"&gt;# one line = one assistant response
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requestId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="c1"&gt;# same request is logged 2-3 times
&lt;/span&gt;        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requestId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this outside the conversation. Reading a whole transcript inside the session inflates the very context you are trying to measure. We also had a PreCompact hook that archives the full transcript at each compaction, which let us cross-check seven verdicts against archive timestamps. All seven matched, with a 2 to 6 minute offset from the KST/UTC difference. Without a hook like that you are relying on the formula alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the advisor is the usual surge
&lt;/h2&gt;

&lt;p&gt;An advisor call sends the whole conversation to a stronger model, so the turn's context becomes 2.05 to 2.20 times larger (six observed cases). The mechanism is simple: multiply your context at call time by 2.2. If the result is past the trigger line, compaction fires on the spot. If not, context returns to normal after the response.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Regime&lt;/th&gt;
&lt;th&gt;Advisor calls&lt;/th&gt;
&lt;th&gt;Outcome&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;After setting a cap&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5 compactions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Before the cap (trigger at the model limit)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3 reverted, 1 compacted&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"I used to call the advisor all the time and it was fine" is not a counterexample. It is a record from a regime where the trigger sat at 1M tokens, so 2.2x never reached it. The next largest turn-inflators are the same category: reading a 123KB file whole (about 35k tokens), full log reads, multi-page PDFs, big JSON dumps, &lt;code&gt;ls -R&lt;/code&gt;, and unbounded database queries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the trigger actually sits
&lt;/h2&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%2Fietjqtftpgol7mq5fzgs.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%2Fietjqtftpgol7mq5fzgs.png" alt="Project A has a 89KB fixed head, floor about 130,000, headroom about 35,000, compaction cost about 125,000; Project B has 69KB, about 91,000, about 102,000, about 85,000" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Setting a 240,000 cap does not mean compaction at 240,000. In our environment it fired at about 165,000 tokens, roughly 0.69 times the cap. We saw the transition from "not fired" to "fired" three times: 160,806 to 165,002, then 162,475 to 165,693, then 164,792 to 168,736.&lt;/p&gt;

&lt;p&gt;We also made a mistake here. We overlaid the three observations and wrote "the trigger is above 164,792 and at most 165,002, a 210-token window." Nothing guaranteed the three observations shared the same conditions, and the trigger is not provably a function of the cap alone, since model and tool schema size are mixed in. We dropped the precision claim and kept two things: trigger T is about cap times 0.69, and headroom is T minus the floor after compaction.&lt;/p&gt;

&lt;p&gt;The floor is a project constant driven by your fixed head (global instructions, project instructions, memory), so do not copy someone else's numbers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Project A&lt;/th&gt;
&lt;th&gt;Project B&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fixed head&lt;/td&gt;
&lt;td&gt;89KB&lt;/td&gt;
&lt;td&gt;69KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Floor after compaction&lt;/td&gt;
&lt;td&gt;about 130,000&lt;/td&gt;
&lt;td&gt;about 91,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Headroom (T minus floor)&lt;/td&gt;
&lt;td&gt;about 35,000&lt;/td&gt;
&lt;td&gt;about 102,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost per compaction&lt;/td&gt;
&lt;td&gt;about 125,000&lt;/td&gt;
&lt;td&gt;about 85,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One compaction costs roughly 25k of summary output plus rewriting the floor. With about 35,000 of headroom, reading one 123KB file whole fills it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do, in order of effect
&lt;/h2&gt;

&lt;p&gt;Raising the cap is not at the top of the list.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Call the advisor less often. Drop the habitual "one more check." Keep it before irreversible work such as DDL, deploys, and external changes, where one bad DDL costs more than ten compactions.&lt;/li&gt;
&lt;li&gt;Cap the largest single-turn increase: no whole-file Reads, use offset/limit, and use Grep to locate things.&lt;/li&gt;
&lt;li&gt;Never take tool output whole. Use grep, head, and tail. Stage one of compaction discards exactly this output, so trimming output and the cap are one set.&lt;/li&gt;
&lt;li&gt;Raise the cap only if headroom is genuinely short.&lt;/li&gt;
&lt;li&gt;Shrink the fixed head. A lower floor reduces both headroom pressure and per-compaction cost.&lt;/li&gt;
&lt;li&gt;Write big artifacts to files and pull back only a summary line.&lt;/li&gt;
&lt;li&gt;Make subagents return conclusions only. A report stays in the parent context permanently, about 3.8k each as measured.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two settings worth pairing with the cap:&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;"autoCompactWindow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;240000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"showClearContextOnPlanAccept"&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;If compaction is already looping, look at what went into the previous turn before touching the cap, and do not call the same thing again. Otherwise the loop keeps spinning.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does not transfer
&lt;/h2&gt;

&lt;p&gt;The general parts are the shape of the classifier and the relations T about cap times 0.69 and headroom equals T minus floor. Everything else is our environment. The 50,000 threshold means "about one floor rewrite," so with a much thinner fixed head, lower it to match your own floor. The method also assumes transcripts with per-turn usage values. We have not observed the natural compaction cycle under a cap, which is why we manage the advisor by how many times we call it, not by when.&lt;/p&gt;

&lt;p&gt;The original post, with the extra FAQ and the reasoning behind each number, is here: &lt;a href="https://my-blog.org/claude-code/post/claude-code-autocompact-single-turn?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=claude-code-autocompact-single-turn" rel="noopener noreferrer"&gt;Claude Code auto-compact is caused by one turn, not a full context&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Free tools I keep on my own site: &lt;a href="https://my-blog.org/tools?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=claude-code-autocompact-single-turn" rel="noopener noreferrer"&gt;34 calculators and how-to guides&lt;/a&gt; - loans, severance pay, take-home salary, savings vs deposits. New ones go out in one email, no ads.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>productivity</category>
      <category>llm</category>
    </item>
    <item>
      <title>Cloudflare Workers Pricing: $5 Minimum, 10M Requests Included</title>
      <dc:creator>Sungwoo Lee</dc:creator>
      <pubDate>Sun, 25 Oct 2026 08:17:03 +0000</pubDate>
      <link>https://dev.to/sungwoo_lee_e0f26be4a29fd/cloudflare-workers-pricing-5-minimum-10m-requests-included-16c9</link>
      <guid>https://dev.to/sungwoo_lee_e0f26be4a29fd/cloudflare-workers-pricing-5-minimum-10m-requests-included-16c9</guid>
      <description>&lt;p&gt;As of October 2026, Cloudflare Workers has a Free plan capped at 100,000 requests per day and a Paid plan with a $5 per month minimum that includes 10 million requests and 30 million CPU milliseconds. Past that, requests cost $0.30 per additional million and CPU time costs $0.02 per additional million CPU ms. There is no charge for data transfer or bandwidth.&lt;/p&gt;

&lt;p&gt;I run my own site, my-blog.org, on Workers with D1 and R2, so this is the table I check before adding anything that runs on every request.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two plans side by side
&lt;/h2&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%2F7g9qyk3ilvd756myrlbd.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%2F7g9qyk3ilvd756myrlbd.png" alt="Free: 100,000 requests a day, 10 ms CPU, $0. Paid: 10 million requests, 30 million CPU ms, $5 per month minimum" width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Free&lt;/th&gt;
&lt;th&gt;Paid (Standard)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Requests&lt;/td&gt;
&lt;td&gt;100,000 per day&lt;/td&gt;
&lt;td&gt;10 million per month included, then $0.30 per additional million&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU time&lt;/td&gt;
&lt;td&gt;10 ms per invocation&lt;/td&gt;
&lt;td&gt;30 million CPU ms per month included, then $0.02 per additional million CPU ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duration&lt;/td&gt;
&lt;td&gt;No charge&lt;/td&gt;
&lt;td&gt;No charge or limit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Minimum charge&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$5 per month per account&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Paid has a default max CPU of 30 seconds per invocation, configurable up to 5 minutes. Cron Trigger and Queue consumer invocations can run up to 15 minutes of CPU.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is billed and what is not
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Static assets: requests to static assets are free and unlimited, and they do not count toward Worker request billing.&lt;/li&gt;
&lt;li&gt;Subrequests: Cloudflare does not bill for subrequests your Worker makes.&lt;/li&gt;
&lt;li&gt;WebSockets: a connection is billed as one request (the initial upgrade). Messages routed through the Worker are not counted.&lt;/li&gt;
&lt;li&gt;Bandwidth: no charge for data transfer or bandwidth.&lt;/li&gt;
&lt;li&gt;Caching: with Workers Caching enabled, cache hits are billed at the request rate, but CPU time is billed only on cache misses or bypasses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The practical consequence: a site that serves most pages as static assets pays almost nothing per page view, and the thing to watch is CPU milliseconds on the dynamic routes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The limits that decide your architecture
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Free&lt;/th&gt;
&lt;th&gt;Paid&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Subrequests per invocation&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;10,000 (configurable up to 10M)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory per isolate&lt;/td&gt;
&lt;td&gt;128 MB&lt;/td&gt;
&lt;td&gt;128 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worker size (uncompressed)&lt;/td&gt;
&lt;td&gt;64 MiB&lt;/td&gt;
&lt;td&gt;64 MiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workers per account&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Simultaneous open connections&lt;/td&gt;
&lt;td&gt;6 per invocation&lt;/td&gt;
&lt;td&gt;6 per invocation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 50-subrequest cap on Free is the one that surprises people. A route that fans out to 60 API or database calls fails on Free and works on Paid. The 6 simultaneous connections cap means a seventh is queued, so a &lt;code&gt;Promise.all&lt;/code&gt; over 20 fetches runs in waves.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked estimate
&lt;/h2&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%2Fey2sgz3tj3gv4dya5j19.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%2Fey2sgz3tj3gv4dya5j19.png" alt="$5 minimum plus $4.50 overage for 15 million extra requests is about $9.50 a month" width="799" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Take 25 million requests in a month, with CPU time inside the included 30 million ms. Requests over the included 10 million: 15 million. Overage: 15 x $0.30 = $4.50. Added to the $5 minimum, that is about $9.50 for the month, before any other Cloudflare product you attach.&lt;/p&gt;

&lt;p&gt;That is my own arithmetic from the published rates, not a quote from Cloudflare, and it assumes the $5 minimum is the base charge that includes the allowances. D1 and R2 are billed separately and I have not priced them here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guardrails worth setting
&lt;/h2&gt;

&lt;p&gt;You can cap CPU time per invocation in your Wrangler config or the dashboard to guard against runaway costs. For a first Paid month, set a cap and read the usage page after a few days instead of trusting an estimate.&lt;/p&gt;

&lt;p&gt;If you want to see what a Workers-hosted site feels like from the outside, the tool pages on mine are at &lt;a href="https://my-blog.org/tools?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=cloudflare-workers-pricing-5-dollar-minimum" rel="noopener noreferrer"&gt;my-blog.org/tools&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits of this post
&lt;/h2&gt;

&lt;p&gt;Every figure is from Cloudflare's Workers pricing and limits pages as of October 2026. I did not look up D1, R2 or KV prices, and the Workers limits page I used does not list them. Prices change, so confirm on the pricing page before you budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is Cloudflare Workers free?&lt;/strong&gt; Yes, up to 100,000 requests per day and 10 ms CPU per invocation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does the Paid plan cost?&lt;/strong&gt; A minimum charge of $5 per month per account, which includes 10 million requests and 30 million CPU ms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do static assets cost anything?&lt;/strong&gt; No. Requests to static assets are free and unlimited.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is bandwidth billed?&lt;/strong&gt; No. There is no charge for data transfer or bandwidth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does my Worker fail with many fetches on Free?&lt;/strong&gt; The Free plan allows 50 subrequests per invocation; Paid allows 10,000.&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>serverless</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Cloudflare Workers AI: 10,000 Free Neurons a Day, Then $0.011</title>
      <dc:creator>Sungwoo Lee</dc:creator>
      <pubDate>Sun, 25 Oct 2026 08:01:26 +0000</pubDate>
      <link>https://dev.to/sungwoo_lee_e0f26be4a29fd/cloudflare-workers-ai-10000-free-neurons-a-day-then-0011-4d93</link>
      <guid>https://dev.to/sungwoo_lee_e0f26be4a29fd/cloudflare-workers-ai-10000-free-neurons-a-day-then-0011-4d93</guid>
      <description>&lt;p&gt;As of October 2026, Cloudflare Workers AI gives every account 10,000 Neurons per day at no charge, and anything above that costs $0.011 per 1,000 Neurons on the Workers Paid plan. The free allocation resets daily at 00:00 UTC. If you only need the number to plan around: 10,000 free Neurons per day is 300,000 over a 30-day month, and the first 1,000,000 Neurons beyond that cost $11.&lt;/p&gt;

&lt;p&gt;This post covers the pricing rules, the rate limits that bite before the money does, and the smallest Worker that calls a model.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get for free, and what happens at the limit
&lt;/h2&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%2Fr87wkf2b0jknoxijhb69.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%2Fr87wkf2b0jknoxijhb69.png" alt="10,000 free Neurons per day, 300,000 over 30 days, then $0.011 per 1,000 Neurons" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&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;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free allocation&lt;/td&gt;
&lt;td&gt;10,000 Neurons per day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price above the free allocation&lt;/td&gt;
&lt;td&gt;$0.011 per 1,000 Neurons&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reset&lt;/td&gt;
&lt;td&gt;Daily at 00:00 UTC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Billing above free&lt;/td&gt;
&lt;td&gt;Requires the Workers Paid plan&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Workers Free accounts get the same 10,000 daily Neurons. Going past that requires upgrading to Workers Paid, which has a $5 per month minimum charge. Some models also accept prepaid AI Gateway credits as an alternative to the Paid plan.&lt;/p&gt;

&lt;p&gt;A quick budget check, using only the prices above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1,000,000 Neurons above the free allocation = 1,000 x $0.011 = $11.&lt;/li&gt;
&lt;li&gt;Because the allocation resets daily at 00:00 UTC, a batch job that straddles midnight UTC draws on two separate daily allocations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Per-token prices differ a lot by model
&lt;/h2&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%2Fysla41fapha9nquqb1ca.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%2Fysla41fapha9nquqb1ca.png" alt="Input price per million tokens from $0.027 for llama-3.2-1b to $1.320 for deepseek-v4-pro, about 49x apart" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same Neuron billing sits behind very different per-token prices. Input and output are priced separately, per million tokens:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a class="mentioned-user" href="https://dev.to/cf"&gt;@cf&lt;/a&gt;/meta/llama-3.2-1b-instruct&lt;/td&gt;
&lt;td&gt;$0.027&lt;/td&gt;
&lt;td&gt;$0.201&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a class="mentioned-user" href="https://dev.to/cf"&gt;@cf&lt;/a&gt;/meta/llama-3.1-8b-instruct-fp8-fast&lt;/td&gt;
&lt;td&gt;$0.045&lt;/td&gt;
&lt;td&gt;$0.384&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a class="mentioned-user" href="https://dev.to/cf"&gt;@cf&lt;/a&gt;/openai/gpt-oss-120b&lt;/td&gt;
&lt;td&gt;$0.350&lt;/td&gt;
&lt;td&gt;$0.750&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a class="mentioned-user" href="https://dev.to/cf"&gt;@cf&lt;/a&gt;/deepseek-ai/deepseek-v4-pro-0813&lt;/td&gt;
&lt;td&gt;$1.320&lt;/td&gt;
&lt;td&gt;$3.960&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The cheapest and most expensive rows differ by about 49x on input price ($1.320 divided by $0.027). Pick the smallest model that passes your own test set before you look at anything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rate limits hit before the bill does
&lt;/h2&gt;

&lt;p&gt;Text generation is limited to 300 requests per minute, unless the model requires the Workers Paid plan. For models that do require it, the limit is per account and per model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;20 requests per minute on standard Workers AI billing.&lt;/li&gt;
&lt;li&gt;50 requests per minute with prepaid AI Gateway credits (unified billing).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other task types have their own ceilings, for example text embeddings at 3,000 requests per minute (1,500 for &lt;a class="mentioned-user" href="https://dev.to/cf"&gt;@cf&lt;/a&gt;/baai/bge-large-en-v1.5) and translation at 720 requests per minute. A queue consumer that fans out 100 text-generation calls in one minute to a paid-only model will be throttled at 20 or 50, whatever your budget says.&lt;/p&gt;

&lt;h2&gt;
  
  
  The smallest Worker that calls a model
&lt;/h2&gt;

&lt;p&gt;Add the binding to your Wrangler config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&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;"ai"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"binding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AI"&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;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 call a model from the &lt;code&gt;fetch&lt;/code&gt; handler through &lt;code&gt;env.AI.run&lt;/code&gt;. The model name below is the one used in Cloudflare's getting-started guide:&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;AI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Ai&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;response&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@cf/google/gemma-4-26b-a4b-it&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="na"&gt;messages&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="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Answer in one sentence.&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="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;What is a Neuron in Workers AI billing?&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;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy with &lt;code&gt;npx wrangler login&lt;/code&gt; and then &lt;code&gt;npx wrangler deploy&lt;/code&gt;. &lt;code&gt;npx wrangler dev&lt;/code&gt; runs locally, but local runs still use your Cloudflare account and incur usage charges, so the free allocation is spent while you test.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do first
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Run your real prompts on the smallest model in the table and compare answers before moving up.&lt;/li&gt;
&lt;li&gt;Log the Neuron usage per call for a day so the $0.011 per 1,000 figure turns into your own number.&lt;/li&gt;
&lt;li&gt;If a feature needs a paid-only model, plan around 20 requests per minute, not 300.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I run my own site, my-blog.org, on Cloudflare Workers with D1 and R2. The browser tools on it are served from that stack, and you can try them at &lt;a href="https://my-blog.org/tools?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=cloudflare-workers-ai-free-10000-neurons" rel="noopener noreferrer"&gt;my-blog.org/tools&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits of this post
&lt;/h2&gt;

&lt;p&gt;Prices change. Every number here is from Cloudflare's documentation as of October 2026; check the Workers AI pricing page before you commit a budget. I did not convert Neurons to tokens, because the documentation page I used does not publish a conversion.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is Workers AI free?&lt;/strong&gt; Yes up to 10,000 Neurons per day, on Workers Free or Paid. Beyond that you need Workers Paid at $0.011 per 1,000 Neurons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When does the free allocation reset?&lt;/strong&gt; Daily at 00:00 UTC.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need the Paid plan to try it?&lt;/strong&gt; No. The Free plan includes the 10,000 daily Neurons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why do I get throttled before I run out of Neurons?&lt;/strong&gt; Per-minute rate limits apply separately: 300 requests per minute for text generation, or 20 for paid-only models on standard billing.&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>serverless</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>AI Coding Assistant Free: What 2,000 Completions and 50 Chats Buy</title>
      <dc:creator>Sungwoo Lee</dc:creator>
      <pubDate>Sun, 25 Oct 2026 07:45:46 +0000</pubDate>
      <link>https://dev.to/sungwoo_lee_e0f26be4a29fd/ai-coding-assistant-free-what-2000-completions-and-50-chats-buy-38hk</link>
      <guid>https://dev.to/sungwoo_lee_e0f26be4a29fd/ai-coding-assistant-free-what-2000-completions-and-50-chats-buy-38hk</guid>
      <description>&lt;p&gt;As of October 2026, the free AI coding assistant with a published hard number is GitHub Copilot Free: 2,000 code completions and 50 chat requests (including Copilot Edits) per month. Cursor's free plan is "limited" with no number, and Claude Code has no free plan at all. If your goal is to pay nothing, the realistic options are Copilot Free, Cursor Free, or an open-source agent such as Cline pointed at a local model.&lt;/p&gt;

&lt;p&gt;I checked each vendor's own pricing or repository page on 2026-10-19. Anything I could not confirm is listed at the end instead of guessed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The comparison
&lt;/h2&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%2Fw2lkypnhq8v5r7fgi1nn.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%2Fw2lkypnhq8v5r7fgi1nn.png" alt="Copilot Free has 2,000 completions and 50 chats a month; Cursor Free is limited with no number; Cline has no limit; Claude Code has no free plan" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Free option&lt;/th&gt;
&lt;th&gt;Published limit&lt;/th&gt;
&lt;th&gt;Catch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Copilot Free&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;2,000 completions and 50 chat requests (including Copilot Edits) per month&lt;/td&gt;
&lt;td&gt;Agent usage is described only as "limited", no number&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cursor Free&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;"Limited" Agent and Chat usage, no number&lt;/td&gt;
&lt;td&gt;Third-party models (Claude, GPT, Gemini) are not available on Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cline&lt;/td&gt;
&lt;td&gt;Yes (software)&lt;/td&gt;
&lt;td&gt;None, Apache 2.0 license&lt;/td&gt;
&lt;td&gt;You supply the model: Anthropic, OpenAI, Google, OpenRouter, or local via Ollama or LM Studio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;The free claude.ai plan does not include Claude Code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini CLI&lt;/td&gt;
&lt;td&gt;Open source, Apache 2.0&lt;/td&gt;
&lt;td&gt;Not published on the repository page&lt;/td&gt;
&lt;td&gt;Its README says it was replaced by Antigravity CLI for the unpaid tier on June 18th, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Reading the Copilot numbers
&lt;/h2&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%2Fbnillurvs2y9nhjblmij.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%2Fbnillurvs2y9nhjblmij.png" alt="Copilot Free: 2,000 completions and 50 chat requests per month, about 67 completions a day" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2,000 completions a month is about 67 a day over 30 days (2,000 divided by 30, rounded). That is a few hours of light editing, not a full-time workload. 50 chat requests is under 2 a day. Paid Copilot starts at Pro for $10 per month, with Pro+ at $39 per month, if you outgrow Free.&lt;/p&gt;

&lt;p&gt;Use Free for: tab-completion in one project, a handful of chat questions when you are stuck. Do not plan around it for: refactors that need dozens of chat turns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cursor Free and the third-party model rule
&lt;/h2&gt;

&lt;p&gt;Cursor's pricing page lists Free at $0 with limited Agent and Chat, and states that third-party models are not available on it. Paid plans start at Plus for $8 per month. On the Free plan you are therefore not choosing Claude or GPT; you are using what Cursor provides.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cline: the only one where the limit is yours
&lt;/h2&gt;

&lt;p&gt;Cline is an open-source coding agent (Apache 2.0) available as a VS Code extension, a JetBrains plugin, a CLI, a desktop app, and an SDK. It is not locked to one provider. With a local model through Ollama or LM Studio there is no per-token bill, and the ceiling becomes your hardware and the quality of the model you can run. With a hosted provider you pay that provider's API rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  A decision rule
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Want completions in an editor, zero setup: Copilot Free.&lt;/li&gt;
&lt;li&gt;Want an agent-style editor and can live without Claude or GPT: Cursor Free.&lt;/li&gt;
&lt;li&gt;Want no vendor quota and have hardware for a local model: Cline plus Ollama or LM Studio.&lt;/li&gt;
&lt;li&gt;Want Claude Code specifically: it needs a Pro, Max, Team, Enterprise or Console account; Free does not qualify.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why the word "free" needs a number
&lt;/h2&gt;

&lt;p&gt;Every one of these tools is free to start and capped in a different unit: monthly requests, an unquantified "limited", or your own compute. Before you build a habit around one, find its unit and watch how fast you use it. I write up AI tooling comparisons like this on my English blog, Tangents, at &lt;a href="https://my-blog.org/tangents?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=ai-coding-assistant-free-2000-completions" rel="noopener noreferrer"&gt;my-blog.org/tangents&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits of this post
&lt;/h2&gt;

&lt;p&gt;Vendors change plans often. Cursor's page in particular lists plan names that differ from older write-ups, so check the live page. I did not test output quality of any tool here; this is a limits and pricing comparison only. Other free offerings exist that I did not verify, and I left them out.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is there a fully free AI coding assistant?&lt;/strong&gt; Copilot Free and Cursor Free cost $0 but are capped. Cline is free software but needs a model, which is free only if you run one locally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How many completions does Copilot Free include?&lt;/strong&gt; 2,000 per month, plus 50 chat requests including Copilot Edits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use Claude Code for free?&lt;/strong&gt; No. Claude Code requires a Pro, Max, Team, Enterprise or Console account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Cursor Free include Claude models?&lt;/strong&gt; No. Third-party models are not available on the Free plan.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Free tools I keep on my own site: &lt;a href="https://my-blog.org/tools?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=ai-coding-assistant-free-2000-completions" rel="noopener noreferrer"&gt;34 calculators and how-to guides&lt;/a&gt; - loans, severance pay, take-home salary, savings vs deposits. New ones go out in one email, no ads.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>programming</category>
      <category>tools</category>
    </item>
    <item>
      <title>Claude Code Install: 3 Shell Commands, and the 2 Errors That Trip You</title>
      <dc:creator>Sungwoo Lee</dc:creator>
      <pubDate>Sun, 25 Oct 2026 07:30:04 +0000</pubDate>
      <link>https://dev.to/sungwoo_lee_e0f26be4a29fd/claude-code-install-3-shell-commands-and-the-2-errors-that-trip-you-opm</link>
      <guid>https://dev.to/sungwoo_lee_e0f26be4a29fd/claude-code-install-3-shell-commands-and-the-2-errors-that-trip-you-opm</guid>
      <description>&lt;p&gt;As of October 2026, the recommended way to install Claude Code is the native installer, and the command depends on your shell. macOS, Linux and WSL use &lt;code&gt;curl -fsSL https://claude.ai/install.sh | bash&lt;/code&gt;. Windows PowerShell uses &lt;code&gt;irm https://claude.ai/install.ps1 | iex&lt;/code&gt;. Windows CMD uses &lt;code&gt;curl -fsSL https://claude.ai/install.cmd -o install.cmd &amp;amp;&amp;amp; install.cmd &amp;amp;&amp;amp; del install.cmd&lt;/code&gt;. Run the wrong one and you get an error that looks like a broken installer but is not.&lt;/p&gt;

&lt;p&gt;Everything below is from Anthropic's setup documentation at code.claude.com, read on 2026-10-19.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;OS: macOS 13.0+, Windows 10 1809+ or Windows Server 2019+, Ubuntu 20.04+, Debian 10+, or Alpine Linux 3.19+.&lt;/li&gt;
&lt;li&gt;Hardware: 4 GB+ RAM, x64 or ARM64.&lt;/li&gt;
&lt;li&gt;Network: an internet connection is required.&lt;/li&gt;
&lt;li&gt;Account: Claude Code needs a Pro, Max, Team, Enterprise or Console account. The free claude.ai plan does not include it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Install: pick your shell
&lt;/h2&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%2Fo9nueblo752jdac9xr9l.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%2Fo9nueblo752jdac9xr9l.png" alt="Three install commands: sh for macOS, Linux and WSL; irm for PowerShell; curl install.cmd for CMD" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;macOS, Linux, WSL&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://claude.ai/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Windows PowerShell&lt;/strong&gt; (the prompt shows &lt;code&gt;PS C:\&lt;/code&gt;)&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;irm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://claude.ai/install.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Windows CMD&lt;/strong&gt; (the prompt shows &lt;code&gt;C:\&lt;/code&gt; without &lt;code&gt;PS&lt;/code&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="nb"&gt;curl&lt;/span&gt; &lt;span class="na"&gt;-fsSL &lt;/span&gt;&lt;span class="kd"&gt;https&lt;/span&gt;://claude.ai/install.cmd &lt;span class="na"&gt;-o &lt;/span&gt;&lt;span class="kd"&gt;install&lt;/span&gt;.cmd &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="kd"&gt;install&lt;/span&gt;.cmd &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;del&lt;/span&gt; &lt;span class="kd"&gt;install&lt;/span&gt;.cmd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The installer shows no progress while it downloads. When it finishes, open a new terminal window and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A working install prints a version number such as &lt;code&gt;2.1.211 (Claude Code)&lt;/code&gt;. For a fuller check, run &lt;code&gt;claude doctor&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 2 errors that are really a shell mix-up
&lt;/h2&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%2F9tua8pygvddlfiop6n2j.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%2F9tua8pygvddlfiop6n2j.png" alt="The &amp;amp;&amp;amp; error means CMD command in PowerShell; the irm error means PowerShell command in CMD" width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Error&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;The token '&amp;amp;&amp;amp;' is not a valid statement separator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;You pasted the CMD command into PowerShell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;'irm' is not recognized as an internal or external command&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;You pasted the PowerShell command into CMD&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Check the prompt: &lt;code&gt;PS C:\&lt;/code&gt; is PowerShell, &lt;code&gt;C:\&lt;/code&gt; without &lt;code&gt;PS&lt;/code&gt; is CMD. If the install fails with &lt;code&gt;syntax error near unexpected token '&amp;lt;'&lt;/code&gt; or a &lt;code&gt;403&lt;/code&gt;, the docs point to their troubleshooting page for alternative install methods.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;claude&lt;/code&gt; is "not found" after a successful install, the install directory is not on your PATH yet. Open a new terminal first; if that does not fix it, follow the "Fix your PATH" section of the troubleshooting guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other install methods
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Homebrew&lt;/td&gt;
&lt;td&gt;&lt;code&gt;brew install --cask claude-code&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinGet&lt;/td&gt;
&lt;td&gt;&lt;code&gt;winget install Anthropic.ClaudeCode&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;npm (needs Node.js 22 or later)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npm install -g @anthropic-ai/claude-code&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;apt, dnf, apk&lt;/td&gt;
&lt;td&gt;Signed repositories for Debian, Fedora, RHEL and Alpine&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Do not use &lt;code&gt;sudo npm install -g&lt;/code&gt;; the docs warn it can cause permission issues and security risks. Native installs auto-update in the background. Homebrew, WinGet and Linux package manager installs do not, so run &lt;code&gt;brew upgrade claude-code&lt;/code&gt; or &lt;code&gt;winget upgrade Anthropic.ClaudeCode&lt;/code&gt; yourself. &lt;code&gt;claude update&lt;/code&gt; applies an update immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  First run
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;your-project
claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Code prompts you to log in on first use through the browser. Git for Windows is recommended on native Windows so Claude Code can use the Bash tool; without it, Claude Code uses PowerShell as the shell tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Uninstall
&lt;/h2&gt;

&lt;p&gt;On macOS, Linux and WSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; ~/.local/bin/claude
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; ~/.local/share/claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Settings live in &lt;code&gt;~/.claude&lt;/code&gt; and &lt;code&gt;~/.claude.json&lt;/code&gt;; removing them deletes your settings, allowed tools, MCP server configurations and session history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits of this post
&lt;/h2&gt;

&lt;p&gt;I did not run the installers for this post; the commands are copied from the documentation as of October 2026, and the sample version number is the one shown there. Commands and URLs can change, so prefer the live setup page if anything fails. I write more Claude Code notes, mostly in Korean, at &lt;a href="https://my-blog.org/claude-code?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=claude-code-install-3-shells-2-errors" rel="noopener noreferrer"&gt;my-blog.org/claude-code&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is the Claude Code install command?&lt;/strong&gt; For macOS, Linux and WSL: &lt;code&gt;curl -fsSL https://claude.ai/install.sh | bash&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I install Claude Code on Windows?&lt;/strong&gt; In PowerShell, run &lt;code&gt;irm https://claude.ai/install.ps1 | iex&lt;/code&gt;. In CMD, use the curl-based install.cmd command shown above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I check it worked?&lt;/strong&gt; Open a new terminal and run &lt;code&gt;claude --version&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it need Node.js?&lt;/strong&gt; Not for the native installer. The npm package requires Node.js 22 or later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use the free Claude plan?&lt;/strong&gt; No. Claude Code requires a Pro, Max, Team, Enterprise or Console account.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Free tools I keep on my own site: &lt;a href="https://my-blog.org/tools?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=claude-code-install-3-shells-2-errors" rel="noopener noreferrer"&gt;34 calculators and how-to guides&lt;/a&gt; - loans, severance pay, take-home salary, savings vs deposits. New ones go out in one email, no ads.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>cli</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Cursor vs Claude Code: $8-$200 Plans, but Claude Bills Extra</title>
      <dc:creator>Sungwoo Lee</dc:creator>
      <pubDate>Sun, 25 Oct 2026 07:14:27 +0000</pubDate>
      <link>https://dev.to/sungwoo_lee_e0f26be4a29fd/cursor-vs-claude-code-8-200-plans-but-claude-bills-extra-3c5j</link>
      <guid>https://dev.to/sungwoo_lee_e0f26be4a29fd/cursor-vs-claude-code-8-200-plans-but-claude-bills-extra-3c5j</guid>
      <description>&lt;p&gt;As of October 2026, the biggest difference between Cursor and Claude Code is not the interface, it is how Claude is paid for. Cursor's plans run from $8 to $200 per month, but its included usage pool covers Cursor's own models, and third-party models such as Claude are billed separately at API list price from prepaid credit. Claude Code is included in a Claude Pro ($20 per month, or $17 billed annually) or Max (from $100 per month) subscription, with usage limits that reset on a rolling five-hour window.&lt;/p&gt;

&lt;p&gt;All numbers below come from cursor.com and claude.com / code.claude.com pages I read on 2026-10-19.&lt;/p&gt;

&lt;h2&gt;
  
  
  Side by side
&lt;/h2&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%2Fn2m7qrd27kryrqu71n0z.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%2Fn2m7qrd27kryrqu71n0z.png" alt="Cursor plans: Plus $8, Premium $30, Super $100, Ultra $200 per month" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Cursor&lt;/th&gt;
&lt;th&gt;Claude Code&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What it is&lt;/td&gt;
&lt;td&gt;An AI editor and coding agent&lt;/td&gt;
&lt;td&gt;A terminal agent that also runs in VS Code, JetBrains, a desktop app, and the web&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free option&lt;/td&gt;
&lt;td&gt;Free, $0, limited, no third-party models&lt;/td&gt;
&lt;td&gt;None: the free claude.ai plan does not include it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entry paid plan&lt;/td&gt;
&lt;td&gt;Plus, $8 per month&lt;/td&gt;
&lt;td&gt;Pro, $20 per month ($17 per month billed annually at $200 up front)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Middle tiers&lt;/td&gt;
&lt;td&gt;Premium $30 (4x Plus), Super $100 (15x Plus)&lt;/td&gt;
&lt;td&gt;Max from $100 per month (5x or 20x Pro usage)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top tier&lt;/td&gt;
&lt;td&gt;Ultra $200 (50x Plus)&lt;/td&gt;
&lt;td&gt;Max, up to the 20x option&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Usage reset&lt;/td&gt;
&lt;td&gt;Weekly pool&lt;/td&gt;
&lt;td&gt;Rolling five-hour session window, plus weekly limits on paid plans&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude models&lt;/td&gt;
&lt;td&gt;Third-party, billed at API list price from prepaid credit on Premium and above&lt;/td&gt;
&lt;td&gt;Native&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The billing detail that changes the math
&lt;/h2&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%2Fbeqvfy62r5ui20bpfpry.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%2Fbeqvfy62r5ui20bpfpry.png" alt="Cursor runs from $8 Plus to $200 Ultra; Claude Code from $20 Pro to Max from $100" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On Cursor, paid plans include one weekly pool for Cursor models, shared across Grok and Cursor. The pool lists Grok 4.7, Grok 4.6, Grok 4.5 and Composer 2.5. Claude, GPT and Gemini models are not in the pool. On Premium and above they are billed at API list price from prepaid credit. The Free plan has no third-party models at all, and the Plus plan on the pricing page is described as Grok models only.&lt;/p&gt;

&lt;p&gt;So "Cursor for $30 with Claude" is not one number. It is $30 for the pool plus whatever the Claude tokens cost at API rates.&lt;/p&gt;

&lt;p&gt;On Claude Code, a Pro or Max subscription covers Claude Code usage against limits shared across web, desktop, mobile and Claude Code. If you instead sign in with a Console account, Claude Code charges by API token consumption. Anthropic's cost page gives the average across enterprise deployments as around $13 per developer per active day and $150-250 per developer per month, with costs below $30 per active day for 90% of users.&lt;/p&gt;

&lt;h2&gt;
  
  
  They are not mutually exclusive
&lt;/h2&gt;

&lt;p&gt;Claude Code ships a VS Code extension, and the docs link an install for Cursor as well (&lt;code&gt;cursor:extension/anthropic.claude-code&lt;/code&gt;). Some people run Cursor as the editor and Claude Code as the agent inside it. You pay for each side separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which one to pick
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;You want the cheapest paid start and are fine with Cursor's own models: Plus at $8 per month.&lt;/li&gt;
&lt;li&gt;You want Claude as the default model with predictable subscription pricing: Claude Pro at $20 per month, then Max at $100 if you hit limits.&lt;/li&gt;
&lt;li&gt;You want Claude inside an editor you already like: Claude Code's extension in VS Code or Cursor.&lt;/li&gt;
&lt;li&gt;You are optimizing for team administration: Cursor Teams starts at $30 per seat per month; Claude Team Standard seats are $20 per seat per month billed annually ($25 monthly).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Limits of this post
&lt;/h2&gt;

&lt;p&gt;I compared pricing structure, not output quality. Cursor's plan names differ from older articles (it listed Pro and Pro+ previously; those are now marked legacy), so check the live pricing page. I did not benchmark either tool on a task.&lt;/p&gt;

&lt;p&gt;More notes on AI tooling are on my English blog Tangents at &lt;a href="https://my-blog.org/tangents?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=cursor-vs-claude-code-8-to-200-dollars" rel="noopener noreferrer"&gt;my-blog.org/tangents&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is Cursor or Claude Code cheaper?&lt;/strong&gt; Cursor starts at $8 per month, Claude Code at $20 per month (Pro). If you want Claude models in Cursor, they bill separately at API list price.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use Claude in Cursor?&lt;/strong&gt; Yes on paid tiers above the entry plan, billed at API list price from prepaid credit. Not on Free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Claude Code included in Claude Pro?&lt;/strong&gt; Yes. It is included in Pro, Max, Team and Enterprise, and not in Free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do Cursor usage limits reset?&lt;/strong&gt; The pool refreshes weekly.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Free tools I keep on my own site: &lt;a href="https://my-blog.org/tools?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=cursor-vs-claude-code-8-to-200-dollars" rel="noopener noreferrer"&gt;34 calculators and how-to guides&lt;/a&gt; - loans, severance pay, take-home salary, savings vs deposits. New ones go out in one email, no ads.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cursor</category>
      <category>claude</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Claude Code Pricing: $20 Pro, $100 Max, and a $13/Day API Average</title>
      <dc:creator>Sungwoo Lee</dc:creator>
      <pubDate>Sun, 25 Oct 2026 06:58:50 +0000</pubDate>
      <link>https://dev.to/sungwoo_lee_e0f26be4a29fd/claude-code-pricing-20-pro-100-max-and-a-13day-api-average-1a88</link>
      <guid>https://dev.to/sungwoo_lee_e0f26be4a29fd/claude-code-pricing-20-pro-100-max-and-a-13day-api-average-1a88</guid>
      <description>&lt;p&gt;As of October 2026, you can pay for Claude Code in three ways: a Claude Pro subscription at $20 per month ($17 per month billed annually), a Max subscription from $100 per month, or per-token API billing through a Console account. Anthropic's own cost page puts the average API cost at around $13 per developer per active day and $150-250 per developer per month, with costs below $30 per active day for 90% of users. The free claude.ai plan does not include Claude Code.&lt;/p&gt;

&lt;p&gt;Prices below are from claude.com/pricing and code.claude.com/docs/en/costs, read on 2026-10-19.&lt;/p&gt;

&lt;h2&gt;
  
  
  The plans
&lt;/h2&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%2Fnh0asdwp57otgygj94fq.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%2Fnh0asdwp57otgygj94fq.png" alt="Pro $20 a month, Max from $100 a month, API average around $13 per developer per active day" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Claude Code included&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;$20 per month, or $17 per month billed annually ($200 up front)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;From $100 per month, billed monthly only, 5x or 20x Pro's usage&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team Standard seat&lt;/td&gt;
&lt;td&gt;$20 per seat per month billed annually, $25 monthly&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team Premium seat&lt;/td&gt;
&lt;td&gt;$100 per seat per month billed annually, $125 monthly&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;US$20 per seat per month billed annually, 20-seat minimum, usage charged at API rates on top&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Console (API)&lt;/td&gt;
&lt;td&gt;Per token&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How the limits work on a subscription
&lt;/h2&gt;

&lt;p&gt;Every plan has usage limits that reset on a rolling five-hour session window, and paid plans also have weekly limits. The usage is shared across claude.ai on the web, desktop, mobile, and Claude Code, so a long chat in the browser eats the same allowance as a coding session.&lt;/p&gt;

&lt;p&gt;When you hit it, Claude Code tells you "You've hit your session limit" or "You've hit your weekly limit". These are shared across all models, so switching models with &lt;code&gt;/model&lt;/code&gt; does not restore access. After a model-specific message such as "You've hit your Opus limit", switching to a model outside that family does keep you working. Usage credits let you continue past the plan limit; run &lt;code&gt;/usage-credits&lt;/code&gt; to manage them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What drives the bill
&lt;/h2&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%2F9yefzu94cqg9wfwk6y3n.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%2F9yefzu94cqg9wfwk6y3n.png" alt="Average API cost is around $13 per active day; 90% of users stay below $30 per active day" width="800" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Claude Code sends your full conversation with every request. The cost page lists these causes of usage climbing in a long session:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Long context: a one-line question in a session open all day still draws usage for the whole conversation.&lt;/li&gt;
&lt;li&gt;Cache misses: your first message after a break longer than the cache lifetime reprocesses the full context. The lifetime is an hour on a subscription and drops to five minutes once you draw on usage credits; on an API key it is five minutes by default.&lt;/li&gt;
&lt;li&gt;Subagents and agent teams: each runs its own context. Agent teams use approximately 7x more tokens than standard sessions when teammates run in plan mode.&lt;/li&gt;
&lt;li&gt;Extended thinking: thinking tokens are billed as output tokens.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Five habits that cut the bill
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;/clear&lt;/code&gt; when you switch to unrelated work.&lt;/li&gt;
&lt;li&gt;Use Sonnet for most coding and reserve Opus for complex architecture or multi-step reasoning. Switch with &lt;code&gt;/model&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Lower the effort level with &lt;code&gt;/effort&lt;/code&gt; for simple tasks.&lt;/li&gt;
&lt;li&gt;Check &lt;code&gt;/usage&lt;/code&gt; to see where plan usage goes. It flags behaviors such as long context or cache misses when one accounts for 10% or more of recent usage.&lt;/li&gt;
&lt;li&gt;Keep CLAUDE.md under 200 lines and move specialized instructions into skills, which load only when invoked.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Which plan to start on
&lt;/h2&gt;

&lt;p&gt;If you code a few hours a week, Pro at $20 per month is the entry point. If you hit the five-hour or weekly wall repeatedly, the next step is Max at $100 per month, which is 5x Pro's usage on the lower option. If you want to pay only for what you use, the Console route bills per token and &lt;code&gt;/usage&lt;/code&gt; shows an estimate computed locally at list price, which is not an invoice.&lt;/p&gt;

&lt;p&gt;My practice notes on Claude Code are written in Korean at &lt;a href="https://my-blog.org/claude-code?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=claude-code-pricing-20-100-and-13-per-day" rel="noopener noreferrer"&gt;my-blog.org/claude-code&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits of this post
&lt;/h2&gt;

&lt;p&gt;The $13 and $150-250 figures are enterprise-deployment averages from Anthropic, not a forecast for one person. I did not verify the Max 20x monthly price, so it is not listed. Plans change; check the pricing page before you subscribe.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How much does Claude Code cost?&lt;/strong&gt; $20 per month on Pro ($17 billed annually), from $100 per month on Max, or per token via the API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Claude Code free?&lt;/strong&gt; No. The free claude.ai plan does not include it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the average API cost?&lt;/strong&gt; Around $13 per developer per active day, according to Anthropic's cost page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why did I hit my limit so fast?&lt;/strong&gt; Long contexts, cache misses and subagents all multiply usage. Run &lt;code&gt;/usage&lt;/code&gt; to see which one applies.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Free tools I keep on my own site: &lt;a href="https://my-blog.org/tools?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=claude-code-pricing-20-100-and-13-per-day" rel="noopener noreferrer"&gt;34 calculators and how-to guides&lt;/a&gt; - loans, severance pay, take-home salary, savings vs deposits. New ones go out in one email, no ads.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Why Idle Claude Code Sessions Burn Tokens: 4.7 Turns per Alert</title>
      <dc:creator>Sungwoo Lee</dc:creator>
      <pubDate>Sun, 25 Oct 2026 06:43:14 +0000</pubDate>
      <link>https://dev.to/sungwoo_lee_e0f26be4a29fd/why-idle-claude-code-sessions-burn-tokens-47-turns-per-alert-5fhc</link>
      <guid>https://dev.to/sungwoo_lee_e0f26be4a29fd/why-idle-claude-code-sessions-burn-tokens-47-turns-per-alert-5fhc</guid>
      <description>&lt;p&gt;Your Claude Code session can lose tokens while you are away from the keyboard. The reason is simple: every request re-sends the whole conversation, so anything that creates a new turn costs as much as a turn you typed yourself. In one session we measured, a single cross-session alert triggered 4.7 turns on average. Here are the five things that open turns in an idle session, how to measure them in your own logs, and what to change.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost model in one line
&lt;/h2&gt;

&lt;p&gt;Each request carries the full context. So a session's cost is roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;session cost ~ average context size x number of turns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You control the first factor with &lt;code&gt;/clear&lt;/code&gt; and by splitting work. The second factor is where idle sessions leak: the system can create turns when you are not typing. In a representative session from our logs (471 requests, about 458k tokens of average context), the cache-read total was 216.5M tokens against 0.43M output tokens. The product of context and turns explained almost all of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five ways an idle session gets new turns
&lt;/h2&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%2F9yach1feymonlzteem4f.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%2F9yach1feymonlzteem4f.png" alt="Cache lifetime is 1 hour on a subscription and 5 minutes with an API key or usage credits" width="799" height="265"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The first message after the cache expires.&lt;/strong&gt; The prompt cache has a limited lifetime. On a subscription it is 1 hour; with an API key or usage credits the default is 5 minutes. Your first request after that gap re-processes the full context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduled tasks such as &lt;code&gt;/loop&lt;/code&gt;.&lt;/strong&gt; A loop fires on its interval even when you are not working, and each firing sends the full context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Messages from another session.&lt;/strong&gt; If you run several sessions that talk to each other, a message landing in an idle session becomes a new turn there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Goal check-ins.&lt;/strong&gt; A goal that is waiting on background work opens status-check turns. The original post reports a cap of 3 check-ins per goal between your prompts, introduced in v2.1.246; before that there was no limit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background-task and watcher notifications.&lt;/strong&gt; A completion notice, or one line of output from a watch script, wakes the session for a turn each.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The cache expiry is the biggest single line item
&lt;/h2&gt;

&lt;p&gt;We scanned 107 conversation transcripts from one project. Cache-expiry rewrites were the number one source of waste at 11.8%. The largest single rewrite was 778,949 tokens, after a gap of 10.6 hours.&lt;/p&gt;

&lt;p&gt;The practical rule: if you are coming back after more than an hour, &lt;code&gt;/clear&lt;/code&gt; and restart with a short brief instead of typing a follow-up into the old thread. A fresh start is free, and the old context would have been re-written in full anyway. The &lt;a href="https://my-blog.org/claude-code/post/claude-tokens-drain-while-idle?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=claude-tokens-drain-while-idle" rel="noopener noreferrer"&gt;transcript scan behind these numbers&lt;/a&gt; has the method and the caveats.&lt;/p&gt;

&lt;h2&gt;
  
  
  One alert, 4.7 turns
&lt;/h2&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%2Fuhco4cr9x6x3j2xridaq.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%2Fuhco4cr9x6x3j2xridaq.png" alt="In one session 15 alerts triggered 70 requests, about 4.7 per alert, plus 4 cache-expiry rewrites" width="800" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Alerts are the cause people underestimate, because each one looks tiny. A notification arrives, and the chain that follows is: the session wakes, reads it, calls a tool, reads the result, and replies. Every step is a full-context request.&lt;/p&gt;

&lt;p&gt;In one session on 2026-08-23 (172 requests after de-duplication):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;15 alerts arrived from other sessions.&lt;/li&gt;
&lt;li&gt;Those alerts triggered 70 requests, which is 70 / 15, about 4.7 turns per alert.&lt;/li&gt;
&lt;li&gt;The same session had 4 cache-expiry rewrites totaling 619,876 tokens.&lt;/li&gt;
&lt;li&gt;Alert-triggered requests made up 40.9% of that session's cost.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compared head to head, 2 alerts cost about as much as one cache rewrite. If a watcher pings you on every item in a 50-item batch, you have bought 50 wake-ups, not one.&lt;/p&gt;

&lt;p&gt;One more trap: if the alert text is truncated (ours was cut at 200 characters), the session often spends an extra turn reading the rest. Check that your notifier sends the whole message.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure your own sessions
&lt;/h2&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%2Fdt25cvr49ez8edd665j6.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%2Fdt25cvr49ez8edd665j6.png" alt="A session file had 463 assistant lines but only 218 unique request IDs" width="800" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Session logs are JSONL files under &lt;code&gt;~/.claude/projects/&lt;/code&gt;. Lines can be huge, so parse them as JSON line by line; do not grep them with wide regexes. Two details matter. First, one request can span several &lt;code&gt;assistant&lt;/code&gt; lines, so de-duplicate by &lt;code&gt;requestId&lt;/code&gt;. On 2026-10-10 one of our files had 463 &lt;code&gt;assistant&lt;/code&gt; lines but only 218 unique request IDs; skipping de-duplication more than doubles your numbers. Second, count a request as an expiry rewrite when the gap from the previous request exceeds the cache lifetime.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="n"&gt;ttl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;  &lt;span class="c1"&gt;# seconds; use 300 for API key / usage credits
&lt;/span&gt;&lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reqs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;rid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requestId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;usage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;usage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;rid&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;rid&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+00:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;reqs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cache_creation_input_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="n"&gt;reqs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rewrites&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="nf"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reqs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reqs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt;
            &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;total_seconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requests=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reqs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; expiry_rewrites=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rewrites&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tokens=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rewrites&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&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 infers expiry from the time gap alone. It does not verify that the cache really expired, so treat the output as an estimate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cut the idle spend
&lt;/h2&gt;

&lt;p&gt;In rough order of impact:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;/clear&lt;/code&gt; when the topic changes.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;/clear&lt;/code&gt; before resuming after a long break instead of continuing the old thread.&lt;/li&gt;
&lt;li&gt;Check the Loops entry in &lt;code&gt;/usage&lt;/code&gt; for scheduled tasks that keep firing while you are idle.&lt;/li&gt;
&lt;li&gt;Hold cross-session messages that do not need an immediate answer.&lt;/li&gt;
&lt;li&gt;Make watchers wake the session once, on completion or on abort, rather than on every success line.&lt;/li&gt;
&lt;li&gt;Make sure alerts include the full text so no extra turn is needed to read the rest.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full write-up, including the cost-model derivation and the transcript scan, is in &lt;a href="https://my-blog.org/claude-code/post/claude-tokens-drain-while-idle?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=claude-tokens-drain-while-idle" rel="noopener noreferrer"&gt;the original post on my-blog.org&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Free tools I keep on my own site: &lt;a href="https://my-blog.org/tools?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=claude-tokens-drain-while-idle" rel="noopener noreferrer"&gt;34 calculators and how-to guides&lt;/a&gt; - loans, severance pay, take-home salary, savings vs deposits. New ones go out in one email, no ads.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claude</category>
      <category>ai</category>
      <category>productivity</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Claude Code Automation: Publish Gates Blocked 23 Bad Posts in 10 Days</title>
      <dc:creator>Sungwoo Lee</dc:creator>
      <pubDate>Sun, 25 Oct 2026 05:00:58 +0000</pubDate>
      <link>https://dev.to/sungwoo_lee_e0f26be4a29fd/claude-code-automation-publish-gates-blocked-23-bad-posts-in-10-days-5fh3</link>
      <guid>https://dev.to/sungwoo_lee_e0f26be4a29fd/claude-code-automation-publish-gates-blocked-23-bad-posts-in-10-days-5fh3</guid>
      <description>&lt;p&gt;If Claude Code runs anything that publishes, posts, or deletes for you, don't rely on a prompt that says "be careful." Put a small script between the model and the irreversible step, and make that script read the actual output. In 10 days (2026-09-28 to 2026-10-07) one such gate ran 248 times on my Claude Code-run blog pipeline and blocked 37 of those runs. That stopped 23 different posts before they went live.&lt;/p&gt;

&lt;p&gt;None of those numbers come from a model's "done!" message. They come from append-only JSON ledgers that every script writes on every run. Below are the counts, the four failures that forced each rule, and the steps to copy. The longer write-up, with the full tables, is on &lt;a href="https://my-blog.org/tangents/post/claude-code-automation-publish-gates?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=claude-code-automation-publish-gates" rel="noopener noreferrer"&gt;the original post&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I measured in 10 days
&lt;/h2&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%2F5f1zyz5svjljxp8mj60r.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%2F5f1zyz5svjljxp8mj60r.png" alt="The publish gate ran 248 times, blocked 37 runs, and stopped 23 posts, of which 11 were fixed and passed" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Claude Code writes and edits the Python scripts, a Windows scheduler runs them, and each script appends one JSON line per run under &lt;code&gt;data/ops/&lt;/code&gt;. I counted the ledgers line by line on 2026-10-07.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gate runs (247 Tistory, 1 dev.to)&lt;/td&gt;
&lt;td&gt;248&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Passed (exit 0)&lt;/td&gt;
&lt;td&gt;206&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blocked (exit 1, defect found)&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Checker itself broke (exit 2)&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Distinct posts checked&lt;/td&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Posts blocked at least once&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blocked, later fixed and passed&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Over a longer window, 49 days with runs, the scheduled jobs logged 1,243 unattended runs across 44 jobs. 997 reported success with output, 207 reported success with zero output, and 39 reported failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the gate blocked posts
&lt;/h2&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%2Fpdla3o57o5r3t24uycoo.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%2Fpdla3o57o5r3t24uycoo.png" alt="Blocked runs by defect: kw-demand 21, fig-embed 6, rel-date 4, kw-grade 3, fig-numbers 3, fig-count 1" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One run can carry two defects, so the reasons add up to 38 across the 37 blocked runs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;kw-demand&lt;/code&gt; (21): the keyword-demand research file was missing or older than 45 days.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fig-embed&lt;/code&gt; (6): charts were rendered but never inserted into the post body.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rel-date&lt;/code&gt; (4): relative dates like "this weekend" that turn false if publishing slips a day.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kw-grade&lt;/code&gt; (3): the target keyword was below the 200 monthly-search bar.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fig-numbers&lt;/code&gt; (3): a number in a chart appeared nowhere in the post text.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fig-count&lt;/code&gt; (1): fewer than 2 charts in the post.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most blocks are boring. That is the point: they are the cheap, mechanical mistakes a model will make while reporting that everything is fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 4 failures that created the rules
&lt;/h2&gt;

&lt;p&gt;Every gate exists because something went wrong while every check said fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. 132 posts shipped with zero images.&lt;/strong&gt; The pipeline had published 132 posts with no images in the body, and every verification run passed. The check looked at the social preview image and exempted any post that had one. The fix was a shared gate that counts charts actually embedded in the body, which is where &lt;code&gt;fig-embed&lt;/code&gt; and &lt;code&gt;fig-count&lt;/code&gt; come from.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A post went live, then the job crashed before writing its ledger.&lt;/strong&gt; The daily job published at 05:01:20 and died at 05:03 with &lt;code&gt;ValueError: I/O operation on closed file&lt;/code&gt;. A late import replaced &lt;code&gt;sys.stdout&lt;/code&gt; with a new wrapper, the old wrapper was garbage-collected, and it closed the shared buffer. No ledger entry meant the next morning's run would have published the same post again. The fix: use &lt;code&gt;sys.stdout.reconfigure(...)&lt;/code&gt; instead, and write the ledger line immediately after each publish rather than at the end of the batch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. A &lt;code&gt;--dry&lt;/code&gt; run rewrote an input file.&lt;/strong&gt; A dry run of a social-follow script overwrote its target list with 30 candidates the owner had already rejected. The next scheduled run would have followed them, and dev.to has no unfollow API. The rule: dry means no state changes, not "different output." If a dry path writes even one file, it is not dry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Code pasted through a shell heredoc broke the publisher twice.&lt;/strong&gt; &lt;code&gt;\n&lt;/code&gt; inside an f-string became a real newline, which was a SyntaxError. Worse, &lt;code&gt;\b&lt;/code&gt; in a regex became the 0x08 control character, so the image counter always returned 0. That one passed syntax checks and surfaced only as "2 charts inserted, 0 images counted." Now any code containing backslashes goes through the file-edit tool, and a control-character scan runs afterward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why gates work where prompts don't
&lt;/h2&gt;

&lt;p&gt;A prompt like "verify your work" asks the model to grade itself. Claude Code is good at writing a checker, but it is not a reliable witness to its own run, because the run's log and the run's result can disagree. Failure 2 is exactly that: the post existed and the record of it didn't.&lt;/p&gt;

&lt;p&gt;A gate separates the two. It opens the output (the HTML, the chart files, the ledger) and returns an exit code, and the publisher refuses to continue on a defect. Claude Code then has a concrete, machine-readable reason to fix something. Of the 23 stopped posts, 11 were fixed and re-passed; the other 12 never passed in this window, so they never went out through the publisher with the defect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copy this setup
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Give every checker three exit codes.&lt;/strong&gt; 0 is clean, 1 is a defect, 2 means the checker itself is broken (no targets found, file unreadable). Finding zero things to check is exit 2, not a pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Append one JSON line per run&lt;/strong&gt; with timestamp, target, exit code, and defect names. If the line count doesn't grow, the check didn't run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make the publish step call the gate&lt;/strong&gt; and stop on exit 1. Put it inside the publisher, not in a checklist someone has to remember.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write the ledger the moment the irreversible action happens&lt;/strong&gt;, not at the end of the batch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restrict what a completion report may say.&lt;/strong&gt; In &lt;code&gt;CLAUDE.md&lt;/code&gt;, a report can cite only the checker's name and exit code, what a human actually opened, and what the gate cannot see.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log every failure as symptom, cause, fix&lt;/strong&gt;, then turn the fix into a check. My 169 incident entries are where most gate rules came from.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A minimal skeleton with the three exit codes and the ledger line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;html_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;defects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;img&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;defects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fig-count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;defects&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;targets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;paths&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;  &lt;span class="c1"&gt;# nothing to check means the checker is broken
&lt;/span&gt;    &lt;span class="n"&gt;worst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gate.jsonl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;defects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;defects&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="n"&gt;worst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;worst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%dT%H:%M:%S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defects&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;defects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;worst&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The publish step then runs this and aborts when the exit code is not 0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits worth knowing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A gate with bad measurement lies with confidence.&lt;/strong&gt; The first live run of a page checker flagged 320 defects across 80 pages. Every one was an analytics beacon failing because the checker opened pages from &lt;code&gt;file://&lt;/code&gt;. When every target fails the same way, suspect the gate first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gates see surface patterns, not quality.&lt;/strong&gt; A title-hook detector rated a good title as having zero hooks because its word list was incomplete, and it blocked a publish. Expect false positives and keep an explicit override.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Success with zero output" is ambiguous.&lt;/strong&gt; 207 runs reported success and produced nothing. Most were legitimate, such as a comment watcher with no new comments, so you must define per job what empty means.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This is one operator's setup,&lt;/strong&gt; a single blog platform over a few weeks. It shows the mechanism works here; it is not a benchmark.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't need 44 jobs to start. Wrap the one script that publishes, posts, or deletes with a checker that exits 1 on the defect you fear most, and ask Claude Code to append a JSON line per run. For the full ledger tables and the incident details, read &lt;a href="https://my-blog.org/tangents/post/claude-code-automation-publish-gates?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=claude-code-automation-publish-gates" rel="noopener noreferrer"&gt;the full post on my-blog.org&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Free tools I keep on my own site: &lt;a href="https://my-blog.org/tools?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=claude-code-automation-publish-gates" rel="noopener noreferrer"&gt;34 calculators and how-to guides&lt;/a&gt; - loans, severance pay, take-home salary, savings vs deposits. New ones go out in one email, no ads.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>automation</category>
      <category>python</category>
      <category>devops</category>
    </item>
    <item>
      <title>4,474 of 5,369 Output Tokens Were Thinking: Reading Claude API Errors</title>
      <dc:creator>Sungwoo Lee</dc:creator>
      <pubDate>Sun, 25 Oct 2026 05:00:22 +0000</pubDate>
      <link>https://dev.to/sungwoo_lee_e0f26be4a29fd/4474-of-5369-output-tokens-were-thinking-reading-claude-api-errors-490h</link>
      <guid>https://dev.to/sungwoo_lee_e0f26be4a29fd/4474-of-5369-output-tokens-were-thinking-reading-claude-api-errors-490h</guid>
      <description>&lt;p&gt;The most confusing Claude API failure is not a 400, a 429, or a 529. It is an HTTP 200 with an empty text block. Nothing was thrown and nothing was logged as an error, yet your JSON parser reports "no JSON found". In our case the cause was thinking tokens eating the whole &lt;code&gt;max_tokens&lt;/code&gt; budget: 4,474 of 5,369 output tokens went to thinking, which left 895 for the answer.&lt;/p&gt;

&lt;p&gt;This post is the triage order we use now. It separates "retry", "fix the request", and "wait", and it catches the failures that never produce an error at all. The longer write-up, with the full status table, is on &lt;a href="https://my-blog.org/claude-code/post/claude-api-errors-empty-response?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=claude-api-errors-empty-response" rel="noopener noreferrer"&gt;the original post&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Errors share one shape, so classify by type
&lt;/h2&gt;

&lt;p&gt;Every API error comes back as the same JSON object: a top-level &lt;code&gt;error&lt;/code&gt; with a &lt;code&gt;type&lt;/code&gt; and a &lt;code&gt;message&lt;/code&gt;, plus a &lt;code&gt;request_id&lt;/code&gt;.&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rate_limit_error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&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;span class="nl"&gt;"request_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"req_..."&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;Branch on &lt;code&gt;error.type&lt;/code&gt;, never on the message text. The docs say wording can change, and the SDK raises a different exception class per status code, so catch the class. A string comparison on the message will break silently one day.&lt;/p&gt;

&lt;p&gt;The first-pass reading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;400 (&lt;code&gt;invalid_request_error&lt;/code&gt;): the request is wrong. Retrying the same request gives the same result.&lt;/li&gt;
&lt;li&gt;429 (&lt;code&gt;rate_limit_error&lt;/code&gt;): a limit of some kind. Which kind matters, see the next section.&lt;/li&gt;
&lt;li&gt;529 (&lt;code&gt;overloaded_error&lt;/code&gt;): temporary overload across all users. Wait and retry. It is not an account problem.&lt;/li&gt;
&lt;li&gt;500 (&lt;code&gt;api_error&lt;/code&gt;) and 504 (&lt;code&gt;timeout_error&lt;/code&gt;): server-side. Back off, or move long requests to streaming or the batch API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One more detail: the SDK already retries connection errors, 429s, and 5xx responses twice with exponential backoff, honoring &lt;code&gt;retry-after&lt;/code&gt;. If an exception reached your code, two more attempts already happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  429 is three different problems
&lt;/h2&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%2Fmy8mp4o2i3ujxg7zdu36.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%2Fmy8mp4o2i3ujxg7zdu36.png" alt="400 means fix the request; 429 with retry-after is a rate limit; 429 without it is a spend cap; 529 is overload" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A 429 can be a rate limit, a monthly spend cap for your usage tier, or a Claude Code workspace spend limit. Only the first one clears if you wait.&lt;/p&gt;

&lt;p&gt;The tell is the &lt;code&gt;retry-after&lt;/code&gt; header. A rate limit sends it. A spend cap does not, and it keeps failing until access reopens. If you treat a spend cap as a rate limit and add backoff, you get a loop that receives the same 429 all day.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;classify_429&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RateLimitError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retry-after&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ra&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cap-type: stop retrying, check spend caps and workspace limits&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rate-type: retry after &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ra&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rarely, a sudden jump in organization usage can trigger a 429 from an acceleration limit. The fix there is to ramp traffic up gradually and keep usage patterns even.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 400 on a valid-looking request
&lt;/h2&gt;

&lt;p&gt;When the payload looks fine, check whether the model dropped a parameter. These are the messages that mattered to us:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Message fragment&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"does not support assistant message prefill"&lt;/td&gt;
&lt;td&gt;Newer models reject a prefilled last assistant message&lt;/td&gt;
&lt;td&gt;Use structured output or the system prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"thinking.type.enabled" is not supported&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fixed &lt;code&gt;budget_tokens&lt;/code&gt; thinking was removed on newer models&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;thinking: {type: "adaptive"}&lt;/code&gt; plus &lt;code&gt;effort&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"thinking blocks ... cannot be modified"&lt;/td&gt;
&lt;td&gt;You edited or dropped the previous assistant turn's thinking blocks&lt;/td&gt;
&lt;td&gt;Send them back unchanged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tool_choice: type "tool" and "any" are not supported&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Some models reject forced tool calls&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;auto&lt;/code&gt; plus strict tool use&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One observation that is not in the official error docs: our LLM wrapper has a comment saying not to pass &lt;code&gt;temperature&lt;/code&gt;, left behind after an old example that included it returned a 400. Treat that as our environment's experience, not a documented rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Empty response, HTTP 200, no error
&lt;/h2&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%2F6zc59r2xzlvfsrzudeb9.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%2F6zc59r2xzlvfsrzudeb9.png" alt="Of 5,369 output tokens, 4,474 were thinking and 895 were left for the answer" width="800" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thinking tokens count toward &lt;code&gt;max_tokens&lt;/code&gt;. If thinking spends the budget first, the response ends with &lt;code&gt;stop_reason: "max_tokens"&lt;/code&gt; and the text block is truncated or missing. The status is 200 and no exception is raised.&lt;/p&gt;

&lt;p&gt;Our case: a call asking for a content plan as JSON returned an empty string and failed with "JSON not found". We then walked a four-step misdiagnosis:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The log said "JSON parse failed".&lt;/li&gt;
&lt;li&gt;We assumed the prompt was steering the model wrong and rewrote it.&lt;/li&gt;
&lt;li&gt;The same failure came back. It failed three times in a row.&lt;/li&gt;
&lt;li&gt;The real cause was the output budget. The clue was that the raw response was an empty string.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last point is the discriminator. If the prompt had pushed the model toward prose instead of JSON, the raw response would not be empty. Only an empty raw string supports the "thinking used the budget" diagnosis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;diagnose_empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;thinking&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output_tokens_details&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                       &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thinking_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;empty response: stop_reason=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop_reason&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output_tokens&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, thinking=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;thinking&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The field to read is &lt;code&gt;usage.output_tokens_details.thinking_tokens&lt;/code&gt;. When streaming, it only appears on the final &lt;code&gt;message_delta&lt;/code&gt; event.&lt;/p&gt;

&lt;p&gt;There are two official fixes: raise &lt;code&gt;max_tokens&lt;/code&gt; so thinking and text both fit, or lower &lt;code&gt;effort&lt;/code&gt; so thinking spends less. We added a third. We cut what a single call has to produce: instead of "plan N posts at once", we ask for one outline, then one table of contents per post. The call that had failed three times passed on the first try.&lt;/p&gt;

&lt;p&gt;The rule of thumb: if the output must arrive in one piece, use the official fixes. If it can be split, split it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A six-step check order
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Is the status 200? If the result is empty, it is an output budget problem. Read &lt;code&gt;stop_reason&lt;/code&gt; and &lt;code&gt;thinking_tokens&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Is it a 400? Find the parameter name in the message (prefill, &lt;code&gt;thinking.type&lt;/code&gt;, &lt;code&gt;tool_choice&lt;/code&gt;). Do not retry unchanged.&lt;/li&gt;
&lt;li&gt;Is it a 429? &lt;code&gt;retry-after&lt;/code&gt; present means rate-type; absent means cap-type, so stop the loop.&lt;/li&gt;
&lt;li&gt;Is it a 529 or 500? The SDK already retried twice. Wait longer, and record the &lt;code&gt;request_id&lt;/code&gt; if it persists. Give that ID to support.&lt;/li&gt;
&lt;li&gt;Is it a 504 or a long request? Move anything that may exceed ten minutes to streaming or the batch API. Note that an error can still arrive after a streamed 200.&lt;/li&gt;
&lt;li&gt;Is it a Claude Code MCP tool? Check &lt;code&gt;/mcp&lt;/code&gt;, and if only the first call fails, raise &lt;code&gt;MCP_TIMEOUT&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  When the first MCP call fails
&lt;/h2&gt;

&lt;p&gt;If a Claude Code MCP tool fails only on its first call in a session, suspect a cold start. One of our search-console servers times out on the first call and works once a lightweight tool has woken it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;th&gt;What it adjusts&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;MCP_TIMEOUT&lt;/code&gt; env var&lt;/td&gt;
&lt;td&gt;Server startup wait&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MCP_TIMEOUT=10000 claude&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-server &lt;code&gt;timeout&lt;/code&gt; in &lt;code&gt;.mcp.json&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Tool execution time in ms&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"timeout": 600000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MAX_MCP_OUTPUT_TOKENS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tool output token cap&lt;/td&gt;
&lt;td&gt;Default 25,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The caution that matters most: do not retry a timed-out write. A timeout means "result unknown", not "failed". Resending a write that already landed applies the change twice, so check whether it took effect first.&lt;/p&gt;

&lt;p&gt;Two things this table does not cover. The subscription limit messages in the Claude Code UI, such as "You've hit your session limit", are plan notices, not HTTP errors. And any automation that reports "success" while producing nothing belongs to the same family as the empty response.&lt;/p&gt;

&lt;p&gt;For the full status table, the FAQ, and the related posts on this, see &lt;a href="https://my-blog.org/claude-code/post/claude-api-errors-empty-response?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=claude-api-errors-empty-response" rel="noopener noreferrer"&gt;the complete guide on my-blog.org&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Free tools I keep on my own site: &lt;a href="https://my-blog.org/tools?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=claude-api-errors-empty-response" rel="noopener noreferrer"&gt;34 calculators and how-to guides&lt;/a&gt; - loans, severance pay, take-home salary, savings vs deposits. New ones go out in one email, no ads.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claude</category>
      <category>api</category>
      <category>debugging</category>
      <category>llm</category>
    </item>
    <item>
      <title>One Unattended Claude Loop Burned 13 Points of Plan Usage</title>
      <dc:creator>Sungwoo Lee</dc:creator>
      <pubDate>Mon, 19 Oct 2026 23:35:21 +0000</pubDate>
      <link>https://dev.to/sungwoo_lee_e0f26be4a29fd/one-unattended-claude-loop-burned-13-points-of-plan-usage-215d</link>
      <guid>https://dev.to/sungwoo_lee_e0f26be4a29fd/one-unattended-claude-loop-burned-13-points-of-plan-usage-215d</guid>
      <description>&lt;p&gt;An LLM loop with no stop condition ran overnight on my Claude subscription and burned 13 percentage points of plan usage. The problem was not the price per token. Nothing in the code could end the loop, and nobody was watching. This post covers how Claude plan limits actually work, how to read your usage, and the three caps I now put in every unattended loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The limits are shared with the chat app
&lt;/h2&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%2F5nqb7uy0remf5gcfmxgz.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%2F5nqb7uy0remf5gcfmxgz.png" alt="Prompt cache lifetime drops from 1 hour (60 minutes) to 5 minutes when usage credits are used" width="799" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pro and Max subscriptions include Claude Code, and the limits come as two meters: a 5-hour session limit and a 7-day weekly limit. According to Anthropic's help pages, Claude and Claude Code draw on the same pool. Whatever you spend in the chat app shrinks what Claude Code can use, and the reverse is also true.&lt;/p&gt;

&lt;p&gt;That sharing is why an "unexpectedly high" bar in the morning is hard to explain from a terminal alone. If part of the usage came from the app, it will not show up in the terminal breakdown.&lt;/p&gt;

&lt;p&gt;Two details are easy to get wrong when you hit a limit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Switching models only helps when the message names a model (for example an Opus-specific limit). For the generic "all models" message, switching does nothing.&lt;/li&gt;
&lt;li&gt;Your options are to wait, turn on usage credits, or auto-continue after the reset. Auto-continue is configured with &lt;code&gt;/rate-limit-options&lt;/code&gt; on v2.1.234 or newer, and organizations use the &lt;code&gt;autoContinueAtUsageLimit&lt;/code&gt; setting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Usage credits have a side effect: the prompt cache lifetime drops from 1 hour (60 minutes) to 5 minutes, so the same working habits cost more. Also, the monthly API credit included with Max cannot be used to extend Claude Code limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read /usage the right way
&lt;/h2&gt;

&lt;p&gt;Type &lt;code&gt;/usage&lt;/code&gt; inside Claude Code. As a subscriber, ignore the Session block and look at the plan usage bars and the breakdown beneath them. The breakdown has three parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Attribution&lt;/strong&gt;: share of usage by skill, subagent, plugin, and MCP server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Behavior flags&lt;/strong&gt;: things like long contexts and cache misses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loops&lt;/strong&gt;: runs and tokens per scheduled task (v2.1.242 or newer).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Press &lt;code&gt;d&lt;/code&gt; for the last 24 hours and &lt;code&gt;w&lt;/code&gt; for the last 7 days. One caveat: these numbers come from local records on the machine you are on. Other devices and claude.ai app usage are missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the overnight incident looked like
&lt;/h2&gt;

&lt;p&gt;On 2026-08-01 an LLM loop ran overnight without anyone approving it, and plan usage went up by 13 percentage points. The 13 is a reading from the meter, a before/after change. I deliberately did not convert it to a token count, because any conversion factor would be an estimate. (The &lt;a href="https://my-blog.org/claude-code/post/claude-plan-limits-overnight-loop?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=claude-plan-limits-overnight-loop" rel="noopener noreferrer"&gt;full incident write-up&lt;/a&gt; has the details.)&lt;/p&gt;

&lt;p&gt;The root cause was that the loop could not stop. If a person had been watching, it would have been killed within minutes. Switching it to a cheaper model would not have fixed anything either, because the missing exit condition would still be there, just burning more slowly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put three caps in the loop
&lt;/h2&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%2F833k2dx4bwq1hmdoc3br.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%2F833k2dx4bwq1hmdoc3br.png" alt="Example caps: 3 consecutive failures, 200 calls, 3 hours" width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pick at least two of these and enforce them in code: a consecutive-failure cap, a total-call cap, and a wall-clock cap. The values from my own example are 3 consecutive failures, 200 calls, and 3 hours.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;MAX_FAILS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MAX_CALLS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MAX_SECONDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fails&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fails&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;MAX_FAILS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stopped: 3 consecutive failures&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;calls&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;MAX_CALLS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stopped: 200 calls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;MAX_SECONDS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stopped: 3 hours&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;calls&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;fails&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;fails&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;done&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The check runs before every iteration, and the function returns the reason it stopped so you can log it. Test the guards first: feed &lt;code&gt;step&lt;/code&gt; a function that always raises, and confirm you get the failure message after exactly 3 attempts.&lt;/p&gt;

&lt;p&gt;One trap: a 429 cooldown is not a cap. It is a resume mechanism, because it revives the loop in every new limit window. It lets the loop run forever, politely.&lt;/p&gt;

&lt;h2&gt;
  
  
  A checklist before any unattended run
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Write down the expected usage before you start.&lt;/li&gt;
&lt;li&gt;Have a human approve that estimate.&lt;/li&gt;
&lt;li&gt;Set at least two caps and test them.&lt;/li&gt;
&lt;li&gt;On exit, log the stop reason and the usage.&lt;/li&gt;
&lt;li&gt;Once a month, audit every cron job, scheduler entry, and background runner.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The guard only covers loops you wrote yourself. For Claude Code's own &lt;code&gt;/loop&lt;/code&gt;, check the Loops row in &lt;code&gt;/usage&lt;/code&gt; and turn it off or lengthen the interval by hand. And because app usage is not in the breakdown, a high morning bar cannot always be attributed from the screen alone.&lt;/p&gt;

&lt;p&gt;The full write-up, including the FAQ on limits, credits, and model switching, is in the &lt;a href="https://my-blog.org/claude-code/post/claude-plan-limits-overnight-loop?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=claude-plan-limits-overnight-loop" rel="noopener noreferrer"&gt;original post on my-blog.org&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Free tools I keep on my own site: &lt;a href="https://my-blog.org/tools?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=claude-plan-limits-overnight-loop" rel="noopener noreferrer"&gt;34 calculators and how-to guides&lt;/a&gt; - loans, severance pay, take-home salary, savings vs deposits. New ones go out in one email, no ads.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
