
Batch File Tools
- Updated July 3, 2026
- thoeltig/better-base-tools
batch_file_tools is a Claude Code skill for ai & agent building. It helps developers move faster with AI-assisted coding.
Key points
- batch_file_tools
- AI & Agent Building
- AI-coding skill
Batch File Tools by the numbers
- Data as of Jul 7, 2026 (Skillselion catalog sync)
/plugin marketplace add thoeltig/better-base-tools/plugin install batch_file_tools@better-base-toolsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Last updated | July 3, 2026 |
|---|---|
| Repository | thoeltig/better-base-tools ↗ |
What it does
Helps with ai & agent building tasks.
README.md
batch_file_tools
A Claude Code plugin that provides batch-capable file reading and editing via MCP. It replaces individual Read, Edit and Write calls with two multi-file, multi-op tools to cut round-trips, reduce context noise and improve error recovery.
Why
Every native tool call appends its result to the context window, and that overhead compounds across the session. Batching N file reads or M edits into a single call cuts round-trips and reduces accumulated context overhead.
The following results come from a controlled test that ran an identical 4-task workload with native tools and then again with MCP tools:
| Metric | Delta |
|---|---|
| Tool calls | −58% |
| Cache read tokens | −51% |
| Session duration | −45% |
| Effective input tokens | −46% |
Real-world sessions on actual projects confirm those numbers by showing −57% file I/O calls, −91% lines per read call and −47% context loaded. Two things drive the difference:
batch_readtargets only the lines needed rather than loading whole files, pulling 7× less content per read call and keeping the context window from filling prematurely.batch_editbundles multiple files and operations into a single turn. Where native tools require one call per change,batch_editaverages roughly 5 changes per call. In the controlled test 27 native edit calls shrank to 7 with MCP, accomplishing more work in 74% fewer turns and directly accounting for the −45% improvement in session duration.
The table below shows per-session averages measured across three real projects.
| Project | Read calls | Lines / read call | Edit calls | Changes / edit call | Context loaded |
|---|---|---|---|---|---|
| Documentation project¹ (native) | 6 | 907 | 15 | 1 | 4.7M tokens |
| Frontend project² (native) | 15 | 1,820 | 27 | 1 | 4.3M tokens |
| Frontend project² (MCP) | 11 | 252 | 7 | ~5 | 2.3M tokens |
| MCP project³ (MCP) | 13 | 290 | 9 | ~4 | 2.8M tokens |
¹ Documentation analysis project — content-only workload, pure native. ² Angular frontend project — same codebase, split by whether MCP server was registered. ³ better-base-tools (this repo).
Session analysis
This section provides the methodology and detail behind the numbers in Why. Measurements are from two codebases (68 files and 343 files), 124 main sessions combined. Baseline: verbatim mode for reads, fileinfo disabled.
The With vs without batch tools subsection gives the overall session comparison; the other subsections cover the individual savings drivers.
Full native vs batch read & edit tool comparison
Single-file reads are already cheaper
Native Read prefixes every line with N\t — the same verbatim-numbered format regardless of whether slicing is used. batch_read places the line range once in the output header, with no per-line markers in any mode.
Native Read |
batch_read verbatim |
batch_read compact |
|
|---|---|---|---|
| Format | N\t on every line |
range in header once | range in header + whitespace stripped |
| chars / equiv-read | 14k–23k | 5k–9k | lower |
| reduction vs native | — | 2.5–2.8× | >2.8× |
This applies to every batch_read call including single-file reads with no bundling. 61% of all observed batch_read calls were single-file; they still produced 2.5–2.8× fewer chars per read than the native equivalent.
Bundling scales with codebase size
Both tools accept arrays. When the model bundles multiple files or operations into one call the savings compound on top of the format reduction. Bundling rate scales with codebase size; bundling depth (avg items per bundled call) is consistent across projects.
| smaller codebase | larger codebase | |
|---|---|---|
batch_read bundling rate |
39% | 62% |
avg files per bundled batch_read call |
2.9× | 2.9× |
batch_edit bundling rate (multi-file) |
26% | 43% |
avg ops per batch_edit call (all calls) |
2.7× | 3.7× |
Read modes and targeting
| Mode / strategy | Observed share | Use case |
|---|---|---|
verbatim + offset+count |
42–56% | Line-targeted slice; header carries range for replace_range / insert_at_line |
compact full file |
21–24% | Information gathering — cheapest full-file read |
verbatim full file |
15–29% | Exact-whitespace anchor for replace / replace_all |
searchTerm (any mode) |
25–27% | Match-only output; context windows merged into single blocks |
Native Read with slicing still emits N\t on every returned line. batch_read slices with offset+count emit the range once in the header, achieving the same targeting at lower overhead.
Edit operations
Content-anchored ops match a string; line-addressed ops target a line number from a preceding sliced read header. Two op types have no native equivalent.
| Op | Observed share | Native equivalent | Anchor |
|---|---|---|---|
replace |
70–77% | Edit |
compact or verbatim read |
replace_range |
15–20% | none | line range from verbatim+offset+count header |
write |
5–7% | Write |
— |
insert_at_line |
2% | none | line range from verbatim+offset+count header |
replace_all |
1–2% | Edit (all occurrences) |
compact or verbatim read |
17–22% of all edit ops (replace_range + insert_at_line) are only possible via batch_edit. A single-file batch_edit call with multiple ops still cuts round-trips versus one Edit or Write call per change.
Read-before-edit overhead
Native Edit requires a preceding Read to source the old_string anchor — read the file, locate the string, copy it into the call. Correlation analysis across native sessions in the larger codebase shows 46% of native reads to a file were followed by a native edit to the same file. Of those read→edit pairs, 50% occurred within 3 sequential steps, confirming direct setup overhead rather than incidental context gathering.
batch_edit eliminates this coupling. The model can just provide the anchor string without reading the file first. In batch-tool sessions, the same-file read-then-edit sequence drops to 2% of batch_read calls.
Each edit to a known file costs two round-trips with native tools (one read result + one edit result, both appending to context) but costs one round-trip with batch tools.
Anchor failures resolve without re-reads
When a native Edit anchor fails to match, the only recourse is to re-read the file to locate the correct string and retry — a full round-trip for what may be a typo or a minor whitespace difference.
batch_edit applies whitespace-normalized matching before raising an error: tabs, spaces, and collapsed whitespace in the old string are matched against the original file content. If normalized matching also fails, the result includes a nearest_anchor block containing verbatim context around the closest matching region, ready to paste as the corrected old string. The model corrects the anchor and retries in the next turn without re-reading the file.
This matters most in long sessions where files have been modified since the last read: what would be a stale-read error in native tools (requiring a full re-read) is an inline correction in batch tools.
Single-file edits still benefit
Multi-file bundling compounds the savings, but most batch_edit calls target a single file: 74% in the smaller codebase, 57% in the larger. All calls still averaged 2.7× and 3.7× operations per call respectively.
Three changes to one file with native tools means three sequential Edit calls — three round-trips, three result blocks added to context. batch_edit handles all three in one call with one result block. The turn is the same unit of context cost whether it contains one operation or ten.
With vs without batch tools
The table below covers sessions from the larger codebase, split by whether batch tools were active. Task complexity varies between sessions, so this is not a controlled test, but the I/O pattern shift is unambiguous.
| native tools only | with batch tools | Δ | |
|---|---|---|---|
| Avg turns / session | 19.5 | 32.3 | +66% |
| Native Read / session | 5.3 | 0.8 | −85% |
| Native Edit / session | 14.0 | 0.8 | −94% |
| Native Write / session | 1.1 | 0.3 | −73% |
batch_read calls |
— | 12.7 (equiv 26.9) | — |
batch_edit calls |
— | 4.6 (equiv 16.8) | — |
| Total actual I/O calls | 20.4 | 19.2 | −6% |
| Equiv I/O if all native | 20.4 | 45.6 | 2.4× more work, same calls |
| Output tokens / session | 24,454 | 45,771 | +87% |
| Cache read / session | 1,309k | 2,480k | +90% |
Same call budget, 2.4× more I/O work completed. Native Edit — the costliest pattern (read file → extract old_string → call Edit) — dropped 94%. Sessions ran 66% longer, taking on more complex tasks without hitting context limits.
What the output growth reflects
Output tokens per session grew 87% (24,454 → 45,771) with batch tools active. That increase is work completed, not overhead: context budget that native tools spend on delivering file content is instead available for reasoning and code generation. Output is the work the model produces to accomplish a task; input is the overhead required to produce it. Reducing input to only the relevant portions frees space for more output.
Orientation compression and cross-session task merging
Before edits can begin, the model needs to orient: read files, understand structure, build enough context to reason about what to change. With native tools, orientation at session start can fill half the context window just to establish what is there and what needs to be done. While editing, the model also has to Read each file before calling Edit because its view of the file may be stale. Orientation and edit-setup reads are interleaved throughout, consuming context budget continuously.
With batch tools, orientation uses fewer tokens before editing can start, and edits no longer require a preceding read. In the larger codebase after switching to batch tools, each session covered 25.8 equivalent file reads per session vs 19.0 before, in 13 actual calls vs 15 — 36% broader coverage at lower per-call cost.
The downstream effect is that tasks which previously required multiple sessions — each needing the context window to fill before execution could begin — can now complete in one. With more context space available and fewer turns needed per action, the model accomplishes more within a single session.
Lightweight discovery reduces this further. fileinfo mode returns size, line count, last-changed date, and extracted refs[] (imports and file references) without delivering any file content. project-intel-tool returns semantic summaries, roles, and dependency maps across the entire project in one query. Both let the model identify which files are worth reading before any content enters the context window, eliminating blind reads of files that turn out to be irrelevant to the task.
Total native projection vs actual (both codebases combined)
| Actual calls | Native-equivalent | Saved | |
|---|---|---|---|
batch_read |
1,136 | 2,220 | 1,084 (49%) |
batch_edit |
456 | 1,398 | 942 (67%) |
| Native tools (Read / Edit / Write / Grep / Glob) | 1,134 | 1,134 | — |
| Total | 2,726 | 4,752 | 2,026 (43%) |
Error rates
Native tool errors increase with codebase size and session length. Batch tool error rates are constant.
| Tool | smaller codebase | larger codebase |
|---|---|---|
Native Write |
18% | 28% |
Native Edit stale-read |
0% | 7% |
batch_read |
1% | 1% |
batch_edit |
1% | 1% |
Native Edit stale-read errors occur when a file changes between the read and the edit call — a window that grows with session length. batch_edit completes the read and write atomically within the same call, eliminating the window.
Context window impact
Tool results accumulate in the context window with every turn. The reductions above compound:
| Driver | Effect |
|---|---|
| 2.5–2.8× fewer chars per read | context fills proportionally more slowly; more turns fit before limits |
| 43% fewer total I/O calls | fewer result blocks per session |
| 66% more turns in batch sessions | longer sessions completing more work without truncation |
replace_range / insert_at_line |
eliminate a full-file re-read before targeted edits, removing one round-trip per targeted change |
A 20-file-read, 15-edit session using native tools adds roughly 600k–800k chars of tool results to context. The equivalent session via batch tools adds roughly 250k–300k — freeing 350k–500k chars that extend session length.
Tools
batch_read
batch_read reads N files in a single call. Each request specifies a mode that controls how much content is returned.
| Mode | Output | Use for |
|---|---|---|
compact (default) |
Single-line collapsed, stripped indent and consecutive whitespace | Information gathering and replace/replace_all anchor — cheapest read; whitespace differences resolved by batch_edit's normalization fallback |
verbatim |
Normalized indentation | Full-file replace/replace_all anchor; sliced reads (offset+count) include <!-- Read line X to Y ... --> header for replace_range/insert_at_line anchoring |
fileinfo |
Fluent-text header <!-- path (Lines: N, Size: N) lastChanged: Xd Yh --> plus optional referenced: line |
Dependency mapping and pre-read sizing — disabled by default, enable via BATCH_TOOLS_READ_ENABLE_FILEINFO=true |
Requests also support glob and directory expansion, offset and count for pagination, and a searchTerm parameter for case-insensitive search (literal string or regex pattern). Search output format depends on count: count=0 (default) returns each match as lineNum\tcontent on a single line; count>0 returns context blocks — nearby windows are merged into one block, single-match blocks are annotated <!-- Line M to N, match at line K -->, merged multi-match blocks use <!-- Line M to N --> only. Files with no matches across a call are merged into a single <!-- No match(es) found --> output block.
Formatting normalization
batch_read normalizes indentation in all modes by default. The goal is token efficiency and model accuracy: normalized output mirrors the style distribution most prevalent in code training data, keeping comprehension high at minimum token cost.
Two rules apply at read time:
- For most file types (TypeScript, JavaScript, CSS, YAML, and similar), indentation is normalized to 2-space. Two spaces is the dominant style across popular open-source JS/TS repositories and public training datasets, and it halves token cost versus 4-space for deeply nested code.
- For tab-required file types such as Makefile, indentation is normalized to one tab per indent level, since tabs carry semantic meaning in these formats and must be preserved.
Project-specific styles — 4-space, 6-space, 3-tab, or any other variant — are normalized on read. Reformatting source files is a mechanical task that belongs to automated tools (Prettier, Black, rustfmt, EditorConfig). Delegating it to the model wastes tokens and context with no accuracy benefit.
Known tradeoff: Python (PEP 8: 4 spaces) and Rust (rustfmt: 4 spaces) are canonical exceptions — their training data is majority 4-space, so normalizing to 2-space marginally deviates from their established style. Token savings outweigh the accuracy delta for most tasks.
Disable globally via BATCH_TOOLS_NORMALIZE_FORMATTING=false (see Configuration) when indentation is itself the subject of an edit.
The following example reads three files in a single call, each with a different mode.
{ "requests": [
{ "path": "/src/auth.ts", "mode": "compact" },
{ "path": "/src/user.ts", "mode": "verbatim", "searchTerm": "validateToken" },
{ "path": "/package.json", "mode": "fileinfo" }
]}
batch_edit
batch_edit applies multiple edits across multiple files in a single call. Operations run in two phases. Line-addressed operations execute first, sorted in descending order by anchor line so that insertions do not shift subsequent anchors. Content-addressed operations follow in input order.
| Op | Description |
|---|---|
replace |
Replaces the first occurrence matched by an exact anchor |
replace_all |
Replaces all occurrences matched by an exact anchor |
insert_at_line |
Inserts content at a specific line number |
replace_range |
Replaces a range of lines |
write |
Overwrites or appends to a file, creating parent directories as needed |
Anchor matching for replace and replace_all uses a two-step fallback: exact string match first; if not found, a whitespace-normalized match (tabs, spaces, and newlines collapsed) against the original file content — the matched original text becomes the replacement target. This is what makes compact mode output a reliable anchor despite its whitespace stripping. Only if both steps fail is a nearest_anchor error returned, containing verbatim context around the closest match pasteable directly as the corrected old string.
stopOnError is configurable at the root, file and op level. The lowest-defined level takes precedence and the default is to continue on error. Dry-run mode can be enabled server-wide via BATCH_TOOLS_DRY_RUN (see Configuration).
When a path falls outside the allowed directories the tool prompts for authorization with per-file options to allow access once or for the remainder of the session.
The following example applies three changes across two files in a single call.
{ "files": [
{ "path": "/src/auth.ts", "ops": [
{ "type": "replace", "old": "const timeout = 30", "new": "const timeout = 60" },
{ "type": "insert_at_line", "line": 15, "content": " logger.debug('auth');\n" }
]},
{ "path": "/src/config.ts", "ops": [
{ "type": "replace_all", "old": "AUTH_TIMEOUT", "new": "SESSION_TIMEOUT" }
]}
]}
Configuration
All options can be set via environment variable or command-line argument. Args accept --<name>=<value>, --<name> <value>, or bare --<name> (sets value to true). Environment variables take precedence over args.
| Env var | Arg | Default | Description |
|---|---|---|---|
BATCH_TOOLS_MCP_LOGGING |
--mcp-logging |
false |
Route log output through the MCP logging protocol instead of console.error. Some harnesses do not support MCP logging; leave disabled unless yours does. |
BATCH_TOOLS_MCP_ANNOTATIONS_USER_AUDIENCE |
--user-audience |
false |
Append a compact human-readable summary to each tool result (e.g. "Read 5 — compact: 3, fileinfo: 2"). Requires the harness to honour annotations.audience; when unsupported the summary is also visible to the model as redundant context. |
BATCH_TOOLS_READ_META |
--read-meta |
{} |
JSON object merged into the _meta field of the batch_read tool registration. Use for harness-specific flags, e.g. {"anthropic/maxResultSizeChars":500000,"anthropic/alwaysLoad":true}. |
BATCH_TOOLS_EDIT_META |
--edit-meta |
{} |
JSON object merged into the _meta field of the batch_edit tool registration. Same format as BATCH_TOOLS_READ_META. |
BATCH_TOOLS_NORMALIZE_FORMATTING |
--normalize-formatting |
true |
Normalize indentation on read (see Formatting normalization). Disable when indentation is itself being edited. |
BATCH_TOOLS_DRY_RUN |
--dry-run |
false |
Run batch_edit without writing any files. All ops are validated and results are reported as if changes were applied. |
BATCH_TOOLS_READ_ENABLE_FILEINFO |
--read-enable-fileinfo |
false |
Enable the fileinfo read mode. When disabled, fileinfo is absent from the schema and tool description entirely. Enable for workflows that need pre-read size checks or dependency mapping via refs[]. This mode is intended to complement project-intel-tool. To avoid model tool choice confusion, fileinfo is disabled by default. |
BATCH_TOOLS_MCP_STRUCTURED_CONTENT |
--mcp-structured-content |
false |
Include the raw result object as structuredContent in tool responses alongside content[]. Some harnesses surface structuredContent to the model instead of content[], which re-wraps text and escapes newlines — leave disabled unless your harness handles both correctly. |
Requirements
This project requires Node.js >= v22 and the following dependencies:
Production Dependencies
@modelcontextprotocol/sdk(1.29.0) — Model Context Protocol SDKzod(3.25.76) — Schema validation
Development Dependencies
typescript(5.9.3) — Static typingvitest(4.1.5) — Testing framework@types/node(25.0.1) — Type definitions for Node
Version History
See CHANGELOG.md for complete version history.
License
See root LICENSE for details.
Support
- Issues: Report bugs or request features
- Repository: better-base-tools
Author: Thore Höltig