Skip to content

Send binary files via application/octet-stream in boxel-cli sync (CS-11075)#4852

Open
FadhlanR wants to merge 3 commits into
mainfrom
cs-11075-ensure-that-uploading-binary-files-to-realm-works-reliably
Open

Send binary files via application/octet-stream in boxel-cli sync (CS-11075)#4852
FadhlanR wants to merge 3 commits into
mainfrom
cs-11075-ensure-that-uploading-binary-files-to-realm-works-reliably

Conversation

@FadhlanR
Copy link
Copy Markdown
Contributor

Summary

  • boxel-cli's realm-sync code read every file as UTF-8 and wrote every downloaded file back as UTF-8, silently corrupting PNG / JPEG / PDF / font bytes (invalid UTF-8 sequences round-trip as U+FFFD).
  • Branch the upload + download sites on isBinaryFilename (already exported by runtime-common) and route binary files through a per-file POST with Content-Type: application/octet-stream — exactly the wire format packages/host's FileUploadService and WriteBinaryFileCommand already use, which the realm-server routes to upsertBinaryFile.
  • The /_atomic batch endpoint stays text-only (it embeds content inside a JSON string). When a push batch contains binary files, they are carved out and POSTed per-file in parallel alongside the atomic call for the text files. Failures are folded back into the existing perFile error shape so callers don't have to learn a second contract.
  • The single-file boxel file write / boxel file read commands grow a symmetric branch: write() now accepts Uint8Array and switches to octet-stream; read() populates a new bytes?: Uint8Array field for binary paths and renders bytes to stdout (or base64 under --json).

Test plan

  • pnpm lint clean in packages/boxel-cli (the runtime-common https://cardstack.com/base/* resolution errors that surface during lint:types are pre-existing and unrelated to this change).
  • pnpm test:unit-exclude-smoke — 187/187 unit tests pass.
  • packages/boxel-cli/tests/integration/realm-push.test.ts — 23/23 pass (4 new binary tests: PNG, PDF, mixed batch, SVG-stays-text).
  • packages/boxel-cli/tests/integration/realm-pull.test.ts — 13/13 pass (1 new binary PNG test).
  • packages/boxel-cli/tests/integration/realm-sync.test.ts — 16/16 pass (no new tests; regression coverage for the partitioned upload path).
  • packages/boxel-cli/tests/integration/realm-watch.test.ts — 15/15 pass (1 new binary PNG test).
  • packages/boxel-cli/tests/integration/file-write.test.ts — 5/5 pass (2 new binary tests: PNG, PDF).
  • packages/boxel-cli/tests/integration/file-read.test.ts — 5/5 pass (1 new binary PNG test).
  • End-to-end manual smoke: push a folder containing .png + .pdf + .woff2 + .gts, pull into an empty dir, hash both sides with shasum -a 256 to confirm byte-equality. (Steps documented in docs/cs-11075-binary-upload-plan.md.)

Test fixture is a 67-byte 1x1 transparent PNG defined inline in packages/boxel-cli/tests/helpers/binary-fixtures.ts — the IDAT chunk contains non-UTF-8 byte sequences (0x89, embedded nulls) that get mangled if anything in the pipeline forces UTF-8 round-tripping, so a byte-level Buffer.equals assertion is enough to catch any regression.

Linear: https://linear.app/cardstack/issue/CS-11075

🤖 Generated with Claude Code

Make boxel-cli's push / pull / sync / watch and the single-file
`boxel file write` / `boxel file read` commands handle binary assets
(images, PDFs, fonts, etc.) byte-identically by mirroring the host
package's wire format: a per-file POST with
`Content-Type: application/octet-stream` and raw bytes as the body,
which the realm-server already routes to `upsertBinaryFile`.

Detection uses the existing `isBinaryFilename` helper from
runtime-common, so SVG (XML-based) stays on the text path while
`image/*`, `font/*`, `application/pdf`, and `.eot` switch to bytes.
The /_atomic batch endpoint stays text-only — binary entries are
carved out and POSTed per-file alongside the atomic call, matching
the host package which never sends binary through /_atomic.

Adds binary roundtrip tests across realm-push, realm-pull,
realm-watch, file-write, and file-read using an inline 1x1
transparent PNG fixture so non-UTF-8 bytes (PNG signature, IDAT
chunk) round-trip verbatim.

Linear: CS-11075

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7a60cacb53

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/boxel-cli/src/lib/realm-sync-base.ts
FadhlanR and others added 2 commits May 18, 2026 16:21
When uploadFilesAtomic split binaries out of /_atomic, a partial
binary failure left the atomic text successes unrecorded on disk —
the next push hit a 409 against the files the server already had.
Record result.succeeded unconditionally in push and sync, drop the
brittle status-from-message regex in favor of a status field on the
thrown Error (with the response body in the message), and reject
source/destination binary-classification mismatches in `file write`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The bare `from '@cardstack/runtime-common'` pulled the whole barrel
into the bundle and the type-check program: transpile.ts → content-tag,
plus ~50 modules that import `https://cardstack.com/base/*`. That
broke the smoke tests (`ENOENT: dist/content_tag_bg.wasm` at startup)
and `lint:types` (TS2307 cascade). Switching to
`/infer-content-type` keeps the bundle to the leaf file's tiny graph,
matching the subpath-only convention every other boxel-cli source
file follows. Bundle shrinks from 7.2 MB to 370 KB; lint clean;
integration suite green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@FadhlanR FadhlanR requested a review from a team May 19, 2026 04:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant