# Claude Code Logs Too Long? Testing Context Diet's Error Limits

> Canonical: https://vxi.me/en/posts/claude-code-context-diet-log-compression/
> Locale: en-us
> Published: 2026-10-11
> Updated: 2026-10-11
> Markdown: https://vxi.me/en/posts/claude-code-context-diet-log-compression.md

A Claude Code session can produce hundreds of lines of `PASS` output for one failing test. The real problem is getting the important failure in front of the agent without forcing it to run the whole command again. Simply showing the last few lines is not a safe answer: the exception may have appeared much earlier.

I came across [Context Diet on Hacker News](https://news.ycombinator.com/item?id=50024608). The project uses a Claude Code Mod hook to replace long tool responses with shorter, tool-aware digests, while leaving a path to the original output. I liked that idea. But the README's promise to retain "every failure" raised a question: what happens when distinct errors exceed the digest's character budget?

I pinned the analysis to [Context Diet v0.3.0, commit `689284a`](https://github.com/drkokorev/context-diet/tree/689284a893f1aa509b9c6c1c06b91ef1ca52279c), inspected the implementation, and tested a manually adapted JavaScript version of its generic log reducer on artificial logs. **I did not install or execute the Context Diet Mod inside Claude Code, and I did not conduct a model A/B test.** The results below apply to these deliberately constructed algorithm fixtures, not to real-world plugin error rates or token savings.

## What gets compressed, and what remains recoverable

The [`session.append` hook](https://github.com/drkokorev/context-diet/blob/689284a893f1aa509b9c6c1c06b91ef1ca52279c/plugins/context-diet/hooks/register.tsx) handles eligible large tool-result messages: Bash, Grep, Glob and JSON-like MCP output. Read, Edit, Write, WebFetch and MCP prose are outside this cutting path. Its default trigger is **8,000 characters**, not 8,000 tokens.

There isn't just one trimming rule. The [pinned parsers](https://github.com/drkokorev/context-diet/blob/689284a893f1aa509b9c6c1c06b91ef1ca52279c/plugins/context-diet/hooks/tools.ts) recognize common Jest, Vitest, pytest, Go test, Cargo and TypeScript-checker formats, promoting failures over passing lines. Other paths condense JSON to a skeleton or summarize diffs, grep matches and paths. These are **source-inspected strategies**; I did not run all of those frameworks in a live Claude Code session.

The useful safety mechanism is recovery. Before substituting a digest, the hook tries to save the full tool text as `.context-diet/out-NNN.txt`, then includes the file path in the message. There are **300 rotating slots**. If saving fails, it lets the original tool result through instead of presenting a broken link.

But "saved locally" doesn't mean "archived forever." Old slots are reused; the files may contain environment variables, credentials and private project paths. A `.gitignore` entry is not encryption or a backup policy. Keep incident-critical raw logs in an independent location.

## Failure-first is not failure-complete

The [generic `dietLog` implementation](https://github.com/drkokorev/context-diet/blob/689284a893f1aa509b9c6c1c06b91ef1ca52279c/plugins/context-diet/hooks/diet.ts) folds repeated lines, reserves room for the head and tail, and searches backward through error windows, trying to include nearby context. Then comes a simple budget guard:

```ts
const cost = window.reduce((n, i) => n + (folded[i]?.text.length ?? 0) + 1, 0)
if (used + cost > target) continue
```

An error window that doesn't fit is skipped. With one failure this can be very effective. With dozens of different errors and large traces, some error text won't be visible in the digest. Dedicated Jest/pytest parsers use different, also bounded selection rules. **My tests concern the generic log fallback; they don't measure how many errors the dedicated Jest/pytest path retains.**

A fair reading of "every failure" is that failures should be prioritized and the full text should remain retrievable, not that a finite summary can display every error in every possible input. An agent cannot conclude the full run was clean merely because the compressed preview has no `ERROR` line.

## The synthetic counterexample: 1 failure versus 145

I manually adapted the generic `dietLog` function and its helpers to dependency-free JavaScript, and ran controlled artificial inputs under Node.js v22.16.0. The fuller harness passed **four tests out of four**, exit code 0. It did not use the Claude Mod API.

| Synthetic input | Raw size | Digest size | Observed |
| --- | ---: | ---: | --- |
| Long log with one central failure | 32,087 chars | 1,175 chars | 1/1 error marker visible |
| 145 distinct errors; 1,450-char target | 23,146 chars | 1,419 chars | 7/145 original error lines visible |
| Short output | 45 chars | 45 chars | unchanged |
| `PASS errorHandler.test.ts` control | 52 chars | 52 chars | passing test not classified as an error |

The dense-error digest showed only seven original error lines; the other **138 weren't in that preview**. This is an excerpt of the actual synthetic output, not a traceback from a real application:

```text
… [225 lines cut] …
ERROR CASEei unhandled category_ts in /app/modules/amy/workflow.ts:40 expected stable result bci
ERROR CASEej unhandled category_tt in /app/modules/amz/workflow.ts:40 expected stable result bcj
ERROR CASEek unhandled category_tu in /app/modules/ana/workflow.ts:40 expected stable result bck
…
… [79 lines cut] …
```

There's a subtle counting distinction. The reducer reported "5 of 145 error lines with context"; counting *all* original error lines visible in the digest gave **seven**. Head and tail retention can incidentally expose errors outside the explicitly chosen windows. Those aren't interchangeable measurements.

**1,450 characters is an intentionally restricted experimental target, not the plugin default.** In the pinned v0.3.0 source, the default cutoff that triggers compression is 8,000 characters. For ordinary long output, the digest target is `Math.max(2_000, Math.round(threshold / 2))`, or **4,000 characters** at that default. For output Claude Code has already saved to a file, it's **3,000 characters**. Those budgets can fit more, but our artificial observations cannot establish any real-world failure-loss percentage.

For readers who want to reproduce the mathematical boundary, I also ran a smaller, dependency-free **two-test** budget replay. Its artificial error lines are longer than the first fixture's; it retained **8/145**, not seven. Both tests passed. These are different fixtures, not competing performance benchmarks. The following is the complete verified smaller script: save it as `budget-replay.test.mjs`, then run `node --test budget-replay.test.mjs`.

```js
/**
 * First-party negative control for Context Diet v0.3.0's generic dietLog budget path.
 * Pin: drkokorev/context-diet @ 689284a893f1aa509b9c6c1c06b91ef1ca52279c.
 * Only the error-window budget logic is mirrored, for controlled ASCII log lines.
 * This is NOT an upstream Mod invocation, a benchmark, or a production loss rate.
 * The full adapted dietLog fixture and observed digests are in the separate evidence bundle.
 */
import assert from 'node:assert/strict';
import test from 'node:test';

const id = i => { let s='',n=i+1; while(n){ n--;s=String.fromCharCode(97+n%26)+s;n=Math.floor(n/26); } return s; };
const noise = n => Array.from({length:n},(_,i)=>'step '+id(i)+' completed '+id(i+1200)+' '+i);
const critical = line => /^ERROR\b|^\s+(Expected|Received):/.test(line);
function select(raw,target){
  const folded=raw.split('\n').map(text=>({text,level:critical(text)?2:0}));
  const errorHits=folded.map((l,i)=>l.level===2?i:-1).filter(i=>i>=0);
  const keep=new Set();let used=0;
  const headBudget=target*(errorHits.length?0.1:0.2);
  const tailBudget=target*(errorHits.length?0.25:0.35);
  for(let i=0;i<folded.length&&used<headBudget;i++){keep.add(i);used+=folded[i].text.length+1;}
  let tailUsed=0;
  for(let i=folded.length-1;i>=0&&tailUsed<tailBudget;i--){
    if(!keep.has(i)){keep.add(i);tailUsed+=folded[i].text.length+1;}
  }
  used+=tailUsed;
  let windowsShown=0;
  for(let h=errorHits.length-1;h>=0;h--){
    const at=errorHits[h];const window=[];
    for(let i=at-2;i<=at+3;i++)if(i>=0&&i<folded.length&&!keep.has(i))window.push(i);
    const cost=window.reduce((n,i)=>n+folded[i].text.length+1,0);
    if(used+cost>target)continue; // EXACT budget guard from upstream dietLog.take()
    window.forEach(i=>keep.add(i));used+=cost;windowsShown++;
  }
  return {visible:folded.filter((_,i)=>keep.has(i)).map(l=>l.text).join('\n'),totalCritical:errorHits.length,windowsShown};
}
test('single late/central failure remains visible',()=>{
  const text=[...noise(310),'ERROR ECASEONE impossible transaction transition','  Expected: completed','  Received: pending',...noise(310)].join('\n');
  const out=select(text,2000);
  assert.ok(out.visible.includes('ECASEONE'));
});
test('many distinct failures cannot all fit finite digest budget',()=>{
  const failures=Array.from({length:145},(_,i)=>'ERROR CASE'+id(i)+' unhandled category_'+id(i+400)+' in /app/modules/'+id(i+900)+'/workflow.ts:40 expected stable result '+id(i+1300));
  const raw=[...noise(90),...failures,...noise(90)].join('\n');
  const out=select(raw,1450);
  const retained=failures.filter(line=>out.visible.includes(line)).length;
  assert.ok(retained>0&&retained<failures.length);
  process.stdout.write(JSON.stringify({failure_lines:failures.length,retained,missing:failures.length-retained,digest_budget_chars:1450,mode:'source-budget-window-control-not-plugin-runtime'})+'\n');
});
```

This narrow reproduction mirrors the selection budget, not the whole plugin. Passing two assertions doesn't prove that Claude Code itself successfully integrated or diagnosed anything.

The point isn't that compression is bad. It is that a digest must remain an **index to the original evidence**, not become a substitute for it.

## Use dry first; opt into capture deliberately

The [pinned README](https://github.com/drkokorev/context-diet/blob/689284a893f1aa509b9c6c1c06b91ef1ca52279c/README.md) lists Claude Code **2.1.287+** at that version and shows these installation commands; I haven't independently run them on a real client:

```text
/plugin marketplace add drkokorev/context-diet
/plugin install context-diet@context-diet
```

I would begin with `/diet dry` and `/diet report`. Dry mode is designed to show what would be shortened without replacing the original output. For commands where complete output matters, use `/diet keep <command fragment>` or `/diet off`.

The optional `/diet capture on` solves a different problem: Claude Code itself may have truncated an oversized tool result. For certain simple test/build Bash commands, capture rewrites the command to record combined stdout/stderr in `.context-diet/run-NN.log`, reads it back, and attempts to preserve the exit code. It is **off by default**.

That wrapper can change permission matching for rules such as `Bash(pytest:*)` and may trigger another authorization prompt. Combining streams can affect the ordering seen by the agent; pipelines, multiple commands, redirections and watch mode are excluded. I wouldn't enable capture without testing it on a permission-sensitive or streaming workflow. Disable it with `/diet capture off` when you're done.

The [author's published results](https://github.com/drkokorev/context-diet/blob/689284a893f1aa509b9c6c1c06b91ef1ca52279c/README.md) include seven Q&A tasks answered correctly in each arm, with **13 versus 8 total tool calls**. Across **18 bug-fix agent runs**, the reported average was **8.0 without**, **6.0 with** and **5.8 with capture**. Those are small author-reported aggregates. I didn't independently reproduce the model runs, success rate or token-cost claims.

If long passing-test logs dominate your sessions, Context Diet may be worth evaluating. If you're making incident or merge decisions, re-open the raw output before concluding the failure set is complete. Whether an agent will actually do that—or fix only the errors shown and push a premature PR—remains an open behavioral question.

This is distinct from my earlier work on [organizing CLAUDE.md / AGENTS.md / Skills](https://aitokenhacks.com/posts/claude-md-agents-md-skills-ci/) and [multi-agent state handoffs](https://aitokenhacks.com/posts/ai-agent-orchestration-after-assistants/). Those concern which instructions are loaded and who owns a task. Here the question is simpler: is the text in front of an agent complete evidence, or a pointer back to it?
