Trilha 2 2.3 File Tools
MODULO 2.3

📄 File Tools: Read, Write, Edit

Tres ferramentas de manipulacao de arquivo com um invariante compartilhado: toda escrita requer leitura previa. Dedup, quote normalization e staleness checks.

~50
Minutos
3
Tools
18%
Dedup Hit Rate
25K
Token Cap
1

Three Tools, One Contract

Claude Code prove tres primitivas de manipulacao de arquivo compartilhando um invariante: toda operacao de escrita requer uma leitura previa do arquivo-alvo.

Capability Read Write Edit
Read-only / concurrency-safeYesNoNo
Requires prior Read--YesYes
Handles images nativelyYesNoNo
Handles PDFsIf supportedNoNo
Quote normalization----Yes
Dedup (skip unchanged)Yes----
Token limit enforced25K default----
LSP notificationsNoYesYes
2

Read Tool Deep-Dive

Pagination: offset + limit

Por default Read retorna ate 2000 linhas a partir da linha 1. Para arquivos grandes, use offset (1-based) e limit.

Token Limit Enforcement

Gate de dois estagios: primeiro uma estimativa rapida (sem API call). Se exceder 1/4 do cap, contagem exata via API. Se exceder maxTokens (default 25K), MaxFileReadTokenExceededError e thrown antes do conteudo ser enviado.

THROW, NAO TRUNCATE

Uma abordagem de truncation foi testada e revertida. Truncation produzia ~25K tokens enquanto o throw produz ~100 bytes de erro, reduzindo dramaticamente contexto desperdicado.

Dedup: Como Read Evita Re-envio

readFileState.set(fullFilePath, {
  content,
  timestamp: getFileModificationTime(fullFilePath),
  offset,  // undefined means "full read"
  limit,
})

No proximo Read do mesmo arquivo e range, a tool checa mtime no disco. Se igual, retorna stub leve em vez de re-enviar conteudo.

DADOS: ~18% DE READS SAO RE-READS

Duas copias completas por turno desperdicam cache_creation tokens em cada turno subsequente. O dedup path produz ~100-byte stub vs ate 25K tokens de conteudo. GrowthBook killswitch tengu_read_dedup_killswitch pode desabilitar.

Image, PDF e Notebook Support

FORMATOS SUPORTADOS
  • Images: PNG, JPG, JPEG, GIF, WebP -- resize via sharp, retornados como base64 block multimodal
  • PDFs: pages parameter aceita ranges como "1-5". Hard cap de 20 pages por call
  • Notebooks: .ipynb via mapNotebookCellsToToolResult() -- modelo ve visao unificada
  • macOS: Screenshot filenames com thin space (U+202F) detectados e retried automaticamente
3

Write Tool Deep-Dive

Write e full-content replacement. Cria novo arquivo ou sobrescreve existente inteiramente.

Read-Before-Write Gate

TRES FAILURE MODES
  • errorCode 2: No read in session -- "File has not been read yet."
  • errorCode 2: Read was partial (offset/limit) -- recusa para prevenir overwrite de conteudo nao visto
  • errorCode 3: File modified after read -- "Read it again before attempting to write."

Atomic Write Sequence

// 1. mkdir (async, before critical section)
await fs.mkdir(dir)

// 2. Backup for file history (async, keyed on content hash)
await fileHistoryTrackEdit(...)

// 3. Sync read + staleness check (critical section starts)
meta = readFileSyncWithMetadata(fullFilePath)
if (lastWriteTime > lastRead.timestamp) throw FILE_UNEXPECTEDLY_MODIFIED_ERROR

// 4. Write to disk (critical section ends)
writeTextContent(fullFilePath, content, enc, 'LF')
LF POLICY

Write sempre persiste com LF line endings, independente do arquivo antigo. Uma abordagem anterior que preservava/inferia line endings corrompeu silenciosamente bash scripts quando sobrescrevia CRLF em Linux.

4

Edit Tool e Quote Normalization

Edit faz exact string replacement: encontra old_string e substitui por new_string. So envia o diff -- muito mais barato que Write para mudancas pequenas.

Quote Normalization

Claude nao pode outputtar curly (typographic) quotes -- a API sanitiza. O Edit tool resolve com processo de dois passos:

// normalizeQuotes() under the hood:
str
  .replaceAll('\u2018', "'")  // left single curly
  .replaceAll('\u2019', "'")  // right single curly
  .replaceAll('\u201C', '"')  // left double curly
  .replaceAll('\u201D', '"')  // right double curly

findActualString() normaliza ambos para straight quotes, localiza o match, e retorna a versao original curly-quote do arquivo. preserveQuoteStyle() aplica o mesmo estilo curly-quote ao new_string.

CONTRACTION DETECTION

preserveQuoteStyle() nao converte cegamente todo ' para curly quote. Quando single quote esta entre duas letras Unicode (don't, it's), e tratado como apostrofo e recebe right single curly quote. Usa /\p{L}/u para deteccao.

Desanitization Table

'<fnr>'           -> '<function_results>'
'<n>'             -> '<name>'
'<o>'             -> '<output>'
'<e>'             -> '<error>'
'\n\nH:'          -> '\n\nHuman:'
'\n\nA:'          -> '\n\nAssistant:'
TRAILING WHITESPACE

normalizeFileEditInput() strip trailing whitespace de cada linha do new_string. Excecao: .md e .mdx sao skipped -- Markdown usa dois trailing spaces como hard line break.

5

Write vs Edit: Side-by-Side

WRITE -- Full File
Write({
  file_path: "/src/config.ts",
  content: `import { z } from 'zod'
export const config = {
  maxRetries: 5,   // changed
  timeout: 3000,
  endpoint: "https://...",
}`
})
// Sends ENTIRE file. 150+ lines of tokens.
EDIT -- Surgical
Edit({
  file_path: "/src/config.ts",
  old_string: "maxRetries: 3,",
  new_string: "maxRetries: 5,",
})
// Sends ~20 characters.
// Costs a fraction of Write.
QUANDO USAR WRITE
  • Criando um novo arquivo do zero
  • Substituindo conteudo com versao estruturalmente diferente (rewrite completo)
  • Mudancas tao extensas que construir unique old_string requereria a maior parte do arquivo
6

The Read-Before-Write Contract

4 Fases do Contrato

flowchart LR A["Phase 1\nRead the file\nStore in readFileState\ncontent + timestamp"] --> B["Phase 2\nvalidateInput()\nReject if no read\npartial read or stale"] B --> C["Phase 3\ncall() atomic check\nSync read + mtime\nclose race window"] C --> D["Phase 4\nWrite/Edit executes\nUpdate readFileState\nnew mtime + content"] style A fill:#1e293b,stroke:#3b82f6,color:#e2e8f0 style B fill:#1e293b,stroke:#f59e0b,color:#e2e8f0 style C fill:#1e293b,stroke:#ef4444,color:#e2e8f0 style D fill:#1e293b,stroke:#10b981,color:#e2e8f0
GOTCHA COMUM

Um linter ou formatter rodando no file save pode modificar o arquivo entre Read e Write do Claude. Isso dispara o erro "file has been modified since read". Fix: re-read o arquivo apos formatting completar, ou configure o linter para nao auto-save durante a sessao do Claude.

7

Limits Precedence e Blocked Paths

PRECEDENCIA DE LIMITES (3 NIVEIS)
  1. Env var CLAUDE_CODE_FILE_READ_MAX_OUTPUT_TOKENS -- user-set override, beats everything
  2. GrowthBook flag tengu_amber_wren -- per-org experiment infrastructure
  3. Hardcoded default -- 25,000 tokens / 256 KB

Blocked Device Paths

// Infinite output -- never reach EOF
'/dev/zero', '/dev/random', '/dev/urandom', '/dev/full'

// Blocks waiting for input
'/dev/stdin', '/dev/tty', '/dev/console'

// fd aliases for stdio
'/dev/fd/0', '/dev/fd/1', '/dev/fd/2'

// Safe: /dev/null intentionally allowed

🎯 Resumo e Takeaways

1

Read e a unica tool read-only e concurrency-safe. Write e Edit requerem Read previa completa.

2

Dedup ~18% hit rate evita re-envio retornando ~100-byte stub quando mtime bate com timestamp cached.

3

Token cap 25K usa estimativa rapida primeiro; so faz API call para contagem exata se suspeito.

4

Edit's quote normalization lida com sanitizacao de curly quotes pela API. findActualString() busca com straight quotes mas escreve de volta curly.

5

Double staleness check em validateInput (pre-permission) e em call() (atomic, sync) fecha a race window.

6

Write truncation foi testada e revertida: throw no gate de 256KB e mais barato que enviar 25K tokens inutilizaveis.

2.2 Bash Tool 2.4 Search Tools