fix(compat): unify Read/Edit content hashing — non-UTF-8 files lock into a permanent stale-read loop (#177)#199
Conversation
…UTF-8 stale-read loop (#177) Read recorded hashFile(path) (raw bytes) while Edit/MultiEdit compare hashContent(readFile(utf8)). For any file whose bytes are not UTF-8-roundtrippable (Latin-1 remnants, mixed encodings), the two hashes never agree: Edit rejects with 'modified since you read it', re-reading records the same mismatching byte-hash, and the pair locks into a permanent stale loop that burns the whole step budget. Read now fingerprints hashContent(readFile(utf8)) — the same normalization Edit compares — so a Read->Edit round-trip agrees on non-UTF-8 files while a genuine external modification is still detected. hashFile stays in file-state.ts for callers wanting byte identity. Tests (edit-tools.test.ts, where the Read+Edit interaction lives): a 0xE9-byte fixture survives Read->Edit (lands, no stale rejection); an external modification after Read on the same non-UTF-8 file is still rejected. Part of #196.
📝 WalkthroughWalkthrough
ChangesRead/Edit hashing
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/ai-claude-compat/src/fs-tools.ts (1)
85-85: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoffFull-file buffering for fingerprinting, even on windowed reads.
readFile(safe, 'utf8')now decodes and buffers the entire file into memory purely to compute the fingerprint, even when only a small window (offset/limit) was requested. For very large files this is more memory-intensive than a streaming hash would be (the oldhashFilelikely streamed). This is a reasonable tradeoff to guarantee hash parity withreadForEdit, but worth keeping in mind if large-file reads become a hot path.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ai-claude-compat/src/fs-tools.ts` at line 85, Update the fingerprinting in the file-read flow around init.fileState.record to avoid decoding and buffering the entire file for windowed reads, while preserving hash parity with readForEdit. Reuse the existing streaming hash behavior, such as hashFile, where appropriate and keep the recorded fingerprint unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/ai-claude-compat/src/fs-tools.ts`:
- Line 85: Update the fingerprinting in the file-read flow around
init.fileState.record to avoid decoding and buffering the entire file for
windowed reads, while preserving hash parity with readForEdit. Reuse the
existing streaming hash behavior, such as hashFile, where appropriate and keep
the recorded fingerprint unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: edaf49c7-fa20-477c-833c-1cfa329c6c16
📒 Files selected for processing (2)
packages/ai-claude-compat/src/edit-tools.test.tspackages/ai-claude-compat/src/fs-tools.ts
Closes #177. First of the XS P1 batch from the parity verification (#196).
Problem
Read fingerprinted raw file bytes (
hashFile) while Edit/MultiEdit compare againsthashContent(await readFile(safe, 'utf8')). For any file whose bytes are not UTF-8-roundtrippable (Latin-1 remnants, mixed encodings), the two hashes never agree: Edit rejects with "modified since you read it", re-reading records the same mismatching byte-hash, and the tool pair locks into a permanent stale loop that burns the whole step budget (probe-verified).Fix
Read now fingerprints
hashContent(await readFile(safe, 'utf8'))— the same normalization Edit compares — so a Read→Edit round-trip agrees on non-UTF-8 files, while a genuine external modification is still detected (different decoded content → different hash).hashFilestays infile-state.tsfor callers that want byte identity.Tests (edit-tools.test.ts, where the Read+Edit interaction lives)
0xE9-byte fixture survives Read→Edit (the edit lands, no stale rejection).test:node(compat 252) + biome.Part of #196.
Summary by CodeRabbit