Graft for Claude Code: Cutting Token Use by 42% in Practice
Mark HarbottlePublished inAI·Programming·
August 20, 2026
The AI briefing for <a href="https://tooltechblog.com/when-the-cms-gives-you-the-wrong-image-a-frontend-developers-decision-framework/” title=”When the CMS Gives You the Wrong Image: A Frontend Developer’s Decision Framework”>Developers
Stay up to date with AI tools, model releases, and developer workflows that matter.
Weekly. Free. One click to leave.
SitePoint Premium
Stay Relevant and Grow Your Career in Tech
- Premium Results
- Publish articles on SitePoint
- Daily curated jobs
- Learning Paths
- Discounts to dev tools
7 Day Free Trial. Cancel Anytime.
Claude Code bills by the token, and every tool call it makes, from grep searches to file reads, feeds directly into that cost. For developers running hundreds of tasks per day, unoptimized tokens add up fast on the bill. Graft is an open- tool calls at the shell level, eliminating redundant tokens before they reach the API
What Is Graft and Why Does It Exist?
The Token Cost Problem with Claude Code
Claude Code operates differently from a standard chat-based Anthropic API integration. Rather than receiving a single prompt and returning a response, it orchestrates multi-step workflows by issuing shell commands, running grep across codebases, reading files into context, and assembling results before generating output. Each of these tool calls produces text that gets fed into the model’s context window, and every token in that window costs money.
The problem is that much of this context is redundant. During a single task, Claude Code frequently re-reads the same files across sequential tool calls. Grep results return full matching lines, headers, and surrounding context even when only match counts or small snippets matter. At default settings, a mean task consumes roughly 8,070 tokens, translating to approximately $0.0429 per task (based on the blended token price for the model used in the benchmark; see Benchmarks section for details). That number may seem trivial in isolation, but it compounds quickly. A developer running 200 tasks per day faces over $250 in monthly token costs from a single seat, a substantial portion of which pays for context the model never needed.
How Graft Solves It
Graft is a set of open-yer. When Claude Code initiates a tool call, Graft intercepts the outbound request, deduplicates context that has already been seen in the current session, trims verbose output to only what the model needs, and returns compressed results back into the context window. The framework uses Claude Code’s native hook system to perform these interventions without modifying Claude Code’s internals
In internal SWE-bench Verified benchmarks, Graft cut mean token usage from 8,070 to 4,650 tokens per task, a 42% reduction.
The headline result: in internal SWE-bench Verified benchmarks, Graft cut mean token usage from 8,070 to 4,650 tokens per task, a 42% reduction. At default settings, this optimization did not degrade output quality in the benchmark described above. The SWE-bench Verified score actually improved from 54% to 66%, a 12 percentage points (pp) gain. One hypothesis is that a leaner context window reduces noise; this causal relationship has not been independently validated.
How Graft Hooks Work Under the Hood
Claude Code’s Hook System Explained
Claude Code exposes lifecycle hooks that fire at defined points before and after tool execution. These hooks are shell scripts or executables that receive structured JSON on stdin describing the current tool call, and they return JSON on stdout to modify or replace the tool’s behavior. This mechanism was designed for extensibility, and Graft takes full advantage of it.
Graft targets hook points including PreToolUse, which fires before a tool call executes; PostToolUse, which fires after execution with the tool’s output; and Notification, which fires on informational events that can be used for logging and auditing. A PreOutput hook is also used if supported by your Claude Code version — verify availability with claude hooks list or by consulting your Claude Code release notes. Each hook point serves a distinct role in Graft’s optimization pipeline.
Graft’s Interception Pipeline
Here is the flow. Claude Code initiates a tool call, such as a grep search or file read. The appropriate Graft hook fires, receiving a JSON payload that describes the tool, its arguments, and (for post-execution hooks) its output. The hook script applies its transformation logic, which might mean deduplicating file content that was already read in a prior tool call, summarizing grep output to just match counts and targeted snippets, or enforcing a context window budget that prevents low-value content from consuming token space. The transformed result is returned as JSON, and Claude Code proceeds with the optimized output in its context.
Why does this work? The dominante: the model re-reads the same files multiple times during multi-step debugging or exploration, and it receives full grep output when only a handful of matches are relevant. Graft’s interceptions target these specific patterns
The following example illustrates the data flow. The first block is the JSON payload received by a PostToolUse hook on stdin, and the second block is the trimmed output returned on stdout:
PostToolUse hook — Input received on stdin:
{"hook":"PostToolUse","tool":"grep","args":{"pattern":"authenticate","path":"./src","recursive":true},"output":{"stdout": "src/auth.service.ts:14: async authenticate(user: User) {src/auth.service.ts:27:src/auth.controller.ts:9: return this.authService.authenticate(req.body)src/auth.service.ts:14: async authenticate(user: User) {src/tests/auth.test.ts:31: await service.authenticate(mockUser)src/tests/auth.test.ts:45: await service.authenticate(invalidUser)src/utils/helpers.ts:102:src/utils/helpers.ts:103:","exitCode":0}}PostToolUse hook — Trimmed output returned on stdout:
{"action":"replace","output":{"stdout": "src/auth.service.ts:14: async authenticate(user: User) {src/auth.controller.ts:9: return this.authService.authenticate(req.body)src/tests/auth.test.ts:31: await service.authenticate(mockUser)[3 duplicate/low-relevance matches removed]","exitCode":0}}The input payload contains the raw grep output, including duplicate lines and low-signal matches. The returned payload uses the "action": "replace" directive to substitute a trimmed version, collapsing duplicates and adding a summary note for removed matches. This is the core data flow Graft manipulates at every hook point.
Installing and Configuring Graft
Prerequisites
Before installing Graft, you need:
- Claude Code CLI installed and authenticated (verify with
claude --version; consult Graft’s README for the minimum supported Claude Code version) - Node.js 18.x LTS or later (Graft’s hook scripts use Node.js as their runtime; tested with 18.20.x)
- Git for cloning the repository
- macOS or Linux (Windows path handling is unverified; the
~/.claude/config path and shell commands may differ)
claude --versionnode--versiongit--versionAll three commands should return version numbers without errors. If claude --version reports a version, the CLI is installed; authentication can be confirmed by running any simple Claude Code task.
Step-by-Step Installation
To install Graft, clone the repository, review the install script, then run it. Verify that hooks are registered in Claude Code’s configuration afterward.
Important: Before proceeding, confirm the repository is publicly accessible by visiting https://github.com/graft-dev/graft. If the URL is not reachable, check Graft’s official documentation for the current repository location.
git clone https://github.com/graft-dev/graft.gitcd graftcat install.shchmod +x install.sh && ./install.shcat ~/.claude/hooks.jsonSecurity note: Always review third-party install scripts before executing them. Inspect install.sh to understand what it does — it should register hook entries in your Claude Code configuration and set absolute paths to the hook scripts. It should not require elevated (sudo) permissions.
Windows users: The ~/.claude/hooks.json path is for macOS/Linux. On Windows, the config path may differ; consult Claude Code documentation for the platform-specific location.
After installation, the hooks.json file should contain entries for the hook points. The install script replaces the placeholder paths with your actual clone directory. The resulting file will look similar to this:
{"hooks":{"PreToolUse":[{"command":"node /path/to/graft/hooks/pre-tool-use.js","timeout":5000}],"PostToolUse":[{"command":"node /path/to/graft/hooks/post-tool-use.js","timeout":5000}],"PreOutput":[{"command":"node /path/to/graft/hooks/pre-output.js","timeout":5000}],"Notification":[{"command":"node /path/to/graft/hooks/notification.js","timeout":3000}]}}Each entry specifies the hook script to execute and a timeout in milliseconds. The /path/to/graft/ shown above is a placeholder — after running the install script, these should reflect the actual absolute path where you cloned the repository. Verify with: grep "command" ~/.claude/hooks.json to confirm paths are resolved. If PreOutput is not recognized by your Claude Code version, remove that entry; the remaining hooks provide the primary optimization benefit.
Configuration Options
You control Graft’s behavior through parameters in its configuration. The key settings:
maxGrepLinescaps how many grep result lines pass through before summarization kicks in.deduplicationWindowcontrols how many prior tool calls Graft tracks for content deduplication (unit: number of preceding tool calls).contextBudgetsets an upper bound on total tokens allowed through per tool call.verbositydetermines how much detail Graft’s own logging produces.
The following configuration uses JSONC format (JSON with comments). Strip comments before use with strict JSON parsers, or use a JSONC-aware tool (such as the jsonc-parser npm package) to read this file:
{"hooks":{"PostToolUse":[{"command":"node /path/to/graft/hooks/post-tool-use.js","timeout":5000}]},"graft":{"maxGrepLines":50,"deduplicationWindow":10,"contextBudget":4000,"verbosity":"normal",}}If you need to load this JSONC configuration from code using JSON.parse, you must strip comments first. For production use, prefer a maintained JSONC parser (e.g., the jsonc-parser npm package) over regex stripping:
const fs =require('fs');functionloadGraftConfig(filePath){const raw = fs.readFileSync(filePath,'utf8');const stripped = raw.replace(///[^"]*/g,'');try{returnJSON.parse(stripped);}catch(e){thrownewError(`Failed to parse Graft config at${filePath}:${e.message}`);}}The default configuration provides a balanced tradeoff between token savings and context completeness. The aggressive settings squeeze out more tokens but increase the risk of stripping context the model genuinely needs.
Warning: Setting contextBudget to very low values (e.g., 2500) may silently degrade task quality by truncating context the model needs. Validate aggressive settings against representative tasks from your codebase before committing to them.
If a hook exceeds its configured timeout (value is in milliseconds), Claude Code will proceed with the original, unmodified tool output — consult Claude Code hook documentation to confirm this fallback behavior for your version.
For debugging, individual hooks can be disabled by removing or commenting out their entries in the hooks object. Start with defaults and tighten after observing baseline behavior on your specific codebase.
Running Your First Optimized Task
Before/After Comparison
To see Graft’s impact, run the same task with and without hooks enabled. Consider a concrete task: fixing a failing test in auth.service.ts.
Note: The --token-usage flag must be verified against your Claude Code version: run claude --help | grep token-usage. If unavailable, check your Claude Code dashboard or billing page for token usage data instead.
mv ~/.claude/hooks.json ~/.claude/hooks.json.bakclaude "Fix the failing test in auth.service.ts" --token-usagemv ~/.claude/hooks.json.bak ~/.claude/hooks.jsonclaude "Fix the failing test in auth.service.ts" --token-usageThe --token-usage flag surfaces the token count and estimated cost directly in the terminal. In this example, the optimized run consumed 41.4% fewer tokens while producing the same fix.
Interpreting the Results
On a single task, the difference might be a fraction of a cent. Over a full workday of 50 to 200 tasks, savings reach roughly $81 per month per developer at 150 tasks per day. Tasks where Graft delivers the highest savings are those involving large codebases with many files, grep-heavy exploration phases, and multi-file debugging sessions where Claude Code re-reads shared dependencies repeatedly. Tasks with the lowest savings tend to be small, focused single-file edits where there is little redundant context to eliminate.
A rough formula for estimating monthly savings: multiply average tokens per task by tasks per day by 30 days by cost per token by 0.42. For a developer averaging 150 tasks per day at 8,070 tokens per task and $5.32 per million tokens (verify current rates for your model at anthropic.com/pricing), that works out to roughly $81 per month saved per developer. (8,070 × 150 × 30 × $5.32/1,000,000 × 0.42 ≈ $81.40)
Writing Custom Hooks
Extending Graft with Your Own Filters
Graft’s built-in hooks handle the most common optimization patterns, but project-specific needs often call for custom filters. Common scenarios include ignoring node_modules or vendor directories that Claude Code’s grep inadvertently searches, applying proprietary context rules for sensitive codebases, or enforcing team-specific conventions about which files should never be included in context.
A custom hook script has four parts: a shebang line, stdin parsing to read the JSON payload, transformation logic, and a JSON response on stdout. Custom hooks are placed in the graft/hooks/custom/ directory and registered in hooks.json alongside Graft’s defaults.
Note: The passthrough action instructs Claude Code to use the original tool output unmodified. Confirm this action name against Claude Code hook documentation for your version.
constEXCLUDED_PATTERNS=['node_modules','/.git/','.git/'];constMAX_INPUT_BYTES=10*1024*1024;const chunks =[];let totalBytes =0;process.stdin.setEncoding('utf8');process.stdin.on('data',(chunk)=>{totalBytes +=Buffer.byteLength(chunk,'utf8');if(totalBytes >MAX_INPUT_BYTES){process.stderr.write('[graft]Input exceeded size limit; passing through.');process.stdout.write(JSON.stringify({action:"passthrough"}));process.exit(0);}chunks.push(chunk);});process.stdin.on('end',()=>{const input = chunks.join('');let event;try{event =JSON.parse(input);}catch(e){process.stderr.write(`[graft] JSON parse error:${e.message}`);process.stdout.write(JSON.stringify({action:"passthrough"}));return;}if(event.tool!=='grep'){process.stdout.write(JSON.stringify({action:"passthrough"}));return;}const stdout =(event.output!=null&& event.output.stdout!=null)? event.output.stdout:'';const lines = stdout.length>0? stdout.split(''):[];const filtered = lines.filter(line=>!EXCLUDED_PATTERNS.some(pat=> line.includes(pat)));const removed = lines.length- filtered.length;const filteredOutput = filtered.join('');const summary = removed >0?`[${removed}results from node_modules/.git filtered]`:'';const result ={action:"replace",output:{stdout: filteredOutput + summary,exitCode: event.output?.exitCode ??0}};process.stdout.write(JSON.stringify(result));});Each line serves a clear purpose: the stdin listener accumulates the JSON payload in chunks with a size guard to prevent unbounded memory growth, the JSON.parse call is wrapped in a try/catch to handle malformed input gracefully (with errors logged to stderr), the null guard on event.output.stdout prevents crashes when a tool produces no output, the filter logic removes any grep results originating from vendor directories using includes-based matching to catch relative paths and various path formats, and the response replaces the original output with the cleaned version. The "passthrough" action for non-grep tools ensures the hook does not interfere with other tool types. The exitCode uses nullish coalescing (??) to faithfully preserve exit codes including 0 and 1.
Testing and Debugging Hooks
Graft supports a debug mode that logs the full input and output of every hook invocation, so you can verify hook behavior by reading the log.
Note: Verify --hook-debug support by running claude --help. If unavailable, set verbosity: "debug" in Graft’s configuration and inspect the log file directly.
claude "List all authentication functions" --hook-debugCommon pitfalls to watch for: malformed JSON output from hook scripts will cause Claude Code to ignore the hook silently; blocking hooks that perform network calls or heavy computation can hang until the timeout expires; and overly aggressive filtering can strip context the model genuinely needs, degrading output quality. When a task produces unexpected results with Graft enabled, the debug log is the first place to check.
Benchmarks and Real-World Performance
Controlled Benchmark Results
The headline performance numbers come from SWE-bench Verified, a standardized benchmark for evaluating AI coding agents on real-world software engineering tasks. The benchmark runs a fixed set of tasks against a controlled environment, providing reproducible comparisons.
Note: These benchmarks were conducted internally. The specific Claude model version, benchmark date, and full run configuration should be confirmed against Graft’s project documentation. Results will vary by model, Claude Code version, and task characteristics.
| Metric | Without Graft | With Graft | Change |
|---|---|---|---|
| Mean tokens/task | 8,070 | 4,650 | −42% |
| Cost/task | $0.0429 | $0.0292 | −32% |
| SWE-bench Verified | 54% | 66% | +12 pp |
The cost reduction (32%) is smaller than the token reduction (42%) because input tokens and output tokens are priced differently across Anthropic’s billing tiers. The per-task cost figures above reflect a blended rate based on the input/output token split observed in the benchmark; the exact breakdown depends on the model and pricing tier in use.
When the 42% Claim Doesn’t Apply
The 42% figure is a mean across the SWE-bench task set, which is weighted toward multi-file, medium-to-large repository tasks. In practice, savings vary from roughly 10% to over 50%.
The 42% figure is a mean across the SWE-bench task set, which is weighted toward multi-file, medium-to-large repository tasks. In practice, savings vary from roughly 10% to over 50%. Small, single-file tasks where Claude Code reads one file and makes one edit may see reductions closer to 10-15%, simply because there is less redundancy to eliminate. Already-optimized prompts that tightly scope the task also leave less room for Graft to improve.
On the other hand, monorepos with hundreds of files, multi-file debugging sessions, and exploratory coding tasks where Claude Code runs many sequential grep searches can see savings well above 42%. The key variable is how much redundant context Claude Code would otherwise generate, and that depends entirely on codebase size and task complexity.
Implementation Checklist
- Confirm Claude Code CLI is installed and authenticated (
claude --version) - Verify Node.js 18.x LTS is available (
node --version) - Confirm the Graft repository is accessible at
https://github.com/graft-dev/graft - Clone the Graft repository
- Review
install.shbefore executing (cat install.sh) - Run the install script (
chmod +x install.sh && ./install.sh) - Verify
~/.claude/hooks.jsonis populated with hook entries and all paths are resolved (no/path/to/graft/placeholders) - Confirm
PreOutputhook is supported by your Claude Code version (remove the entry if not) - Run a baseline task without Graft and record token usage
- Enable Graft and rerun the same task
- Compare token counts between both runs
- Adjust
contextBudgetandmaxGrepLinesfor your codebase characteristics - (Optional) Write custom hooks for project-specific filtering
- Monitor the cost dashboard over one week to confirm sustained savings
Limitations and What’s Next
Current Limitations
Graft only works with the Claude Code CLI. Developers using Claude through Cursor, GitHub Copilot, or other wrapper tools cannot use these hooks, as those environments do not expose the same lifecycle hook system. Aggressive filtering occasionally strips context that Claude genuinely needs to reason about a problem. Watch out for this. At extreme contextBudget and maxGrepLines settings, task completion quality may degrade, so validate settings on representative tasks before committing to aggressive configurations. Each hook invocation also adds a small latency overhead on the order of tens of milliseconds per tool call (actual overhead depends on hardware, Node.js version, and payload size; measure with time echo '{}' | node hooks/post-tool-use.js on your system). For most workflows this is imperceptible, but in tasks that trigger hundreds of rapid tool calls, the cumulative overhead may become noticeable.
Roadmap
If Anthropic adopts similar optimization patterns natively within Claude Code, Graft becomes unnecessary. Until that happens, Graft remains the most direct path to cutting token costs without changing how developers interact with Claude Code.
Graft’s maintainers have outlined plans for supporting additional AI coding agents beyond Claude Code, expanding the set of tools whose output can be optimized. A community-contributed hook library is in development, which would allow teams to share and reuse project-specific filters. If Anthropic adopts similar optimization patterns natively within Claude Code, Graft becomes unnecessary. Until that happens, Graft remains the most direct path to cutting token costs without changing how developers interact with Claude Code.
Mark Harbottle is the co-founder of SitePoint, 99designs, and Flippa.


