Skip to content

fix(cli): externalize/internalize corrupt binary files under .squad/ - #1492

Merged
tamirdresher merged 1 commit into
github:devfrom
Sanjays2402:fix/1489-externalize-binary-corruption
Jul 17, 2026
Merged

tamirdresher merged 1 commit into
github:devfrom
Sanjays2402:fix/1489-externalize-binary-corruption

Conversation

@Sanjays2402

Copy link
Copy Markdown
Contributor

Fixes #1489.

squad externalize (and internalize) copy every file under .squad/ by reading it as a UTF-8 string and writing the string back out. Any byte >= 0x80 gets decoded to U+FFFD on the way in, so non-UTF-8 files come out corrupted — and since externalize deletes the source right after the copy, the only intact copy is gone. internalize copies back through the same lossy path, so the round trip never restores the original.

Core state is markdown/JSON today so text-only projects don't hit this, but it bites any binary a user or agent keeps under .squad/ (diagrams/screenshots referenced from decisions, PDFs, archives), and it would destroy the whole state db the moment SqliteStorageProvider (.squad/squad.db) becomes reachable.

The fix swaps the three readSync/writeSync copy sites — the externalize file branch, the internalize file branch, and copyDirRecursive — for storage.copySync, which is copyFileSync underneath and already does the recursive mkdir for the destination. Byte-for-byte copy, no string decode.

Test

Added a regression in test/external-state.test.ts that writes a file containing every byte value 0x00–0xFF (invalid UTF-8), runs externalize then internalize, and asserts the externalized copy and the restored copy are both byte-identical to the original. It fails on main (Buffer.equals is false — the 256-byte file comes back 512 bytes with U+FFFD) and passes with this change. Full test/external-state.test.ts suite is green (19 tests), and tsc --noEmit on both packages is clean.

readSync/writeSync round-trips file contents through a UTF-8 string, so any
byte >= 0x80 becomes U+FFFD. Because externalize deletes the source right
after the copy, non-UTF-8 files are silently destroyed and internalize can't
restore them. Copy bytes via storage.copySync at all three copy sites
(externalize, internalize, copyDirRecursive) instead.

Fixes github#1489
Copilot AI review requested due to automatic review settings July 17, 2026 08:45
@github-actions

Copy link
Copy Markdown
Contributor

🟡 Impact Analysis — PR #1492

Risk tier: 🟡 MEDIUM

📊 Summary

Metric Count
Files changed 3
Files added 1
Files modified 2
Files deleted 0
Modules touched 3

🎯 Risk Factors

  • 3 files changed (≤5 → LOW)
  • 3 modules touched (2-4 → MEDIUM)

📦 Modules Affected

root (1 file)
  • .changeset/fix-externalize-binary-corruption.md
squad-cli (1 file)
  • packages/squad-cli/src/cli/commands/externalize.ts
tests (1 file)
  • test/external-state.test.ts

This report is generated automatically for every PR. See #733 for details.

@github-actions

Copy link
Copy Markdown
Contributor

🛫 PR Readiness Check

ℹ️ This comment updates on each push. Last checked: commit 1b667dc

PR Scope: 📦🔧 Mixed (product + infrastructure)

⚠️ 2 item(s) to address before review

Status Check Details
✅ Single commit 1 commit — clean history
✅ Not in draft Ready for review
✅ Branch up to date Up to date with dev
❌ Copilot review No Copilot review yet — it may still be processing
✅ Changeset present Changeset file found
✅ Scope clean No .squad/ or docs/proposals/ files
✅ No merge conflicts No merge conflicts
✅ Copilot threads resolved No Copilot review threads
❌ CI passing 4 check(s) still running

Files Changed (3 files, +36 −13)

File +/−
.changeset/fix-externalize-binary-corruption.md +5 −0
packages/squad-cli/src/cli/commands/externalize.ts +7 −13
test/external-state.test.ts +24 −0

Total: +36 −13


This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes binary-file corruption during squad externalize / squad internalize by switching the copy mechanism from a UTF-8 string round-trip (readSync/writeSync) to a byte-accurate file copy (storage.copySync). This prevents irreversible data loss for non-UTF-8 files under .squad/ when externalize deletes the originals.

Changes:

  • Replace readSync/writeSync-based file copying in externalize/internalize and copyDirRecursive with storage.copySync to preserve bytes.
  • Add a regression test that round-trips a 0x00–0xFF binary buffer through externalize → internalize and asserts byte equality.
  • Add a changeset for the CLI patch release noting the fix.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
test/external-state.test.ts Adds a regression test ensuring non-UTF-8 binary files survive externalize/internalize byte-for-byte.
packages/squad-cli/src/cli/commands/externalize.ts Switches file copy paths to storage.copySync to avoid UTF-8 decode/encode corruption.
.changeset/fix-externalize-binary-corruption.md Declares a patch release and documents the binary corruption fix.

@tamirdresher tamirdresher added priority:p1 This sprint type:bug Something broken labels Jul 17, 2026
@tamirdresher

Copy link
Copy Markdown
Collaborator

Thanks for the thorough repro and the byte-for-byte fix. Confirmed the corruption on dev and confirmed the three copySync sites cover all lossy paths. Closes #1489.

Non-blocking follow-up for whoever wires up SqliteStorageProvider at .squad/squad.db: this exact code path is the reason we must never reintroduce text-mode copying anywhere in externalize/internalize or copyDirRecursive — the regression test in test/external-state.test.ts should stay as the guard against that.

@tamirdresher tamirdresher left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Externalize binary corruption fix looks correct: text-mode s.readFileSync + trim replaced with copyFileSync (byte-for-byte). Two-pass Buffer-based diff catches the same regression class if it ever comes back. CI (test suite, security review, architectural review, bootstrap protection, diff-size guard, samples build, sdk-exports validation) all green on head 1b667dc. Approving.

@tamirdresher
tamirdresher merged commit 6328e81 into github:dev Jul 17, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority:p1 This sprint type:bug Something broken

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: squad externalize corrupts binary files under .squad/ and deletes the originals

3 participants