<?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: Amadeusz Heller</title>
    <description>The latest articles on DEV Community by Amadeusz Heller (@ziomixdev).</description>
    <link>https://dev.to/ziomixdev</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%2F2457420%2Ff97bb1e7-3640-4a93-876d-dc44fa4cb2bc.png</url>
      <title>DEV Community: Amadeusz Heller</title>
      <link>https://dev.to/ziomixdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ziomixdev"/>
    <language>en</language>
    <item>
      <title>Claude Code's auto-compaction took me 2 minutes, so I moved it to the background</title>
      <dc:creator>Amadeusz Heller</dc:creator>
      <pubDate>Wed, 04 Nov 2026 13:22:32 +0000</pubDate>
      <link>https://dev.to/ziomixdev/claude-codes-auto-compaction-took-me-2-minutes-so-i-moved-it-to-the-background-5i9</link>
      <guid>https://dev.to/ziomixdev/claude-codes-auto-compaction-took-me-2-minutes-so-i-moved-it-to-the-background-5i9</guid>
      <description>&lt;p&gt;If you run long Claude Code sessions you know the moment. The context hits the auto-compact threshold, Claude stops, and you look at "Compacting conversation..." for a minute or two. In my Opus sessions that took 80 to 160 seconds every time, and once 259 seconds.&lt;/p&gt;

&lt;p&gt;So I built a plugin that does the slow part earlier, in the background, while the session keeps working. When auto-compaction fires, it swaps in the summary that is already done. That takes 8-20 ms.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpcq447ny9eupzdmhf80z.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpcq447ny9eupzdmhf80z.gif" alt="Standard auto-compaction blocks the conversation for 1:38; with live-compact it is done in 0.02 s" width="640" height="706"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/SocialBuzzStudio/live-compact" rel="noopener noreferrer"&gt;SocialBuzzStudio/live-compact&lt;/a&gt; (MIT).&lt;/p&gt;

&lt;h2&gt;
  
  
  Why compaction blocks
&lt;/h2&gt;

&lt;p&gt;Standard compaction is one request: the whole conversation plus "summarize this". The input mostly comes from the prompt cache, but the summary itself is long (about 12,000 tokens in my sessions) and the session waits for the last token of it.&lt;/p&gt;

&lt;p&gt;None of that has to happen at the threshold, though. The conversation 40,000 tokens before the threshold is almost the same conversation. Summarize that one, and at the threshold you only have to glue the newest messages onto the summary.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the plugin does
&lt;/h2&gt;

&lt;p&gt;Since Claude Code 2.1.287, &lt;a href="https://code.claude.com/docs/en/plugins/mods/overview" rel="noopener noreferrer"&gt;mods&lt;/a&gt; are on by default: plugins whose TypeScript runs inside Claude Code and can hook engine events, including compaction. live-compact uses three things from that API:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;$.session.usage()&lt;/code&gt; every 5 seconds for the context size, and once a minute for the auto-compact threshold.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$.model.fork({ prompt })&lt;/code&gt; when the context is within 40,000 tokens of the threshold (or 20% of it, whichever is smaller). A fork sends the session's last request plus my prompt, with the same model, so almost all of it is read from the prompt cache: about 330,000 tokens read from cache and 12,000 written per compaction in my sessions. Claude keeps working on your task meanwhile.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;session.compact&lt;/code&gt; hook. When compaction starts, it gets the conversation and can return its own &lt;code&gt;{ messages }&lt;/code&gt; instead of letting the engine summarize.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The hook, simplified:&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="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;session.compact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;agentId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// subagents: standard compaction&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prepared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ready&lt;/span&gt;
  &lt;span class="nf"&gt;reset&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;eligible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trigger&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trigger&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;manual&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instructions&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;prepared&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;eligible&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;compacted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prepared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;headText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prepared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messages&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&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="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// anything doesn't line up: standard compaction&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;compacted()&lt;/code&gt; returns the summary as the first message, then every message after the last one the fork saw, unchanged. If that message is no longer in the conversation (you rewound past it, ran &lt;code&gt;/clear&lt;/code&gt;, resumed), it refuses and the engine compacts the normal way. So the worst case is the same wait as without the plugin.&lt;/p&gt;

&lt;h2&gt;
  
  
  The parts that took the longest
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The fork doesn't see the response in progress.&lt;/strong&gt; It replays the last request, so the summary ends where that request ended, not at the end of the transcript. The anchor is the last user message before the response that is still streaming, and everything after it goes back verbatim.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Messages you type while Claude works.&lt;/strong&gt; The engine files them next to the following tool result, outside the messages the compact hook gets. The first version quietly dropped them. Now the plugin catches them on &lt;code&gt;session.append&lt;/code&gt; and puts their text back into that tool result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skills.&lt;/strong&gt; Standard compaction carries over the skills used earlier in the conversation. A hook that returns its own messages doesn't get that, so the plugin copies them into the summary message, with the same limits as the engine (5,000 tokens each, 25,000 in total).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;--resume&lt;/code&gt;.&lt;/strong&gt; A tool result handed back with its handle keeps its pre-compaction parent, so resuming the session loaded the old history again. I reported it as &lt;a href="https://github.com/anthropics/claude-code/issues/107178" rel="noopener noreferrer"&gt;anthropics/claude-code#107178&lt;/a&gt; and the plugin works around it for text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Numbers
&lt;/h2&gt;

&lt;p&gt;In my everyday sessions on 2 and 3 November (test sessions not counted), 14 of 17 auto-compactions used the plugin's summary, each in 8-20 ms. In the other 3 the summary wasn't ready yet: one step added more than ~40,000 tokens at once (several large files read in one go) and the context passed the threshold first. Those ran standard compaction.&lt;/p&gt;

&lt;p&gt;I haven't compared the quality of the plugin's summaries with Claude Code's own in any systematic way. The prompt follows the same structure, and if your &lt;code&gt;CLAUDE.md&lt;/code&gt; has a &lt;code&gt;# Compact instructions&lt;/code&gt; section, the summary follows it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The context after compaction is bigger by the verbatim tail (usually up to ~40,000 tokens), so the next compaction comes sooner.&lt;/li&gt;
&lt;li&gt;The summary shows up as a user message ("This session is being continued from a previous conversation..."). A plugin can't mark it as a compaction summary the way the engine does.&lt;/li&gt;
&lt;li&gt;A summary that never gets used is a wasted request, for example after &lt;code&gt;/clear&lt;/code&gt; or when you close the session before it compacts. On a subscription it counts toward your usage limits like any other request.&lt;/li&gt;
&lt;li&gt;Images in tool results among the kept messages are dropped; their text stays.&lt;/li&gt;
&lt;li&gt;Your own &lt;code&gt;PreCompact&lt;/code&gt;/&lt;code&gt;PostCompact&lt;/code&gt; hooks may not run when the plugin compacts.&lt;/li&gt;
&lt;li&gt;I've only tested it on Windows.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;In a terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;claude plugin install live-compact --marketplace SocialBuzzStudio/live-compact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the VS Code extension: type &lt;code&gt;/plugins&lt;/code&gt;, add &lt;code&gt;SocialBuzzStudio/live-compact&lt;/code&gt; on the Marketplaces tab, then install live-compact on the Plugins tab.&lt;/p&gt;

&lt;p&gt;Every compaction leaves one line in the conversation saying whether the plugin's summary was used. If you try it on macOS or Linux, an issue with how it went would help a lot.&lt;/p&gt;

&lt;p&gt;Claude Code wrote most of the plugin's code, and this post too, from the project notes. The numbers come from my own everyday sessions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Claude Code's auto-compaction blocked my Opus sessions for 80-160 s. live-compact has the summary written in the background ~40k tokens before the threshold and swaps it in within 8-20 ms when compaction starts; if anything doesn't line up, standard compaction runs. &lt;a href="https://github.com/SocialBuzzStudio/live-compact" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
