# fix\(clients\): large pasted images no longer break later Claude image reads · PR \#19364 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/19364)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: merged

Source repository: pingdotgg/t3code

Source branch: t3/fix-13647-client

Target branch: main

Head commit: 8171969db964f1f112a187fd9508042b1ed9b2d4

[Discussion and reviews](https://git.cafe/pingdotgg/t3code/pull/19364)

[Commits](https://git.cafe/pingdotgg/t3code/pull/19364/commits?format=markdown)

[Files changed](https://git.cafe/pingdotgg/t3code/pull/19364/diffs?format=markdown)

## Description

---

Pasting a screenshot over 2000px into a running Claude turn broke every later image read in that thread. Once a request holds more than 20 images, Anthropic rejects any image over 2000px on a side. Claude Code scales images in a turn's first message, but sends images pasted mid-turn as they are.

Now clients scale the image when it is attached, before upload. Any PNG or JPEG whose long edge is over 2000px is scaled down to 2000px:

- PNG stays PNG and keeps transparency. JPEG stays JPEG at quality 0.92 on web and 0.85 on mobile, the mobile app's existing photo quality. EXIF orientation is applied.
- Images at or under 2000px are not re-encoded. Only the header is read to decide. GIF, WebP, and SVG are not changed.
- If scaling fails (the browser cannot decode the image, or native render fails), the original is attached as before. The fix cannot make attaching worse than today.

Where:

- **Shared rule:** `packages/client-runtime/src/imageAttachmentSize.ts` holds the 2000px edge and the fit math.
- **Web and desktop:** `compressImageToByteLimit` scales first, with `createImageBitmap` and a canvas, asynchronously at attach time. This covers paste, drop, the file picker, HEIC photos, and desktop SnapShots. SnapShot accessibility coordinates already rescale from the returned size. The re-encode for the 10 MB byte cap now also stops at 2000px instead of 2048px.
- **Mobile:** uses `expo-image-manipulator`, which the app already ships. No new native module. It covers the photo library, clipboard paste, native paste, and the share sheet.

The 10 MB upload limit is still checked on the scaled bytes. Previews show the scaled image.

Not covered: images picked on mobile through the Files document picker are stored as file attachments and are not scaled.

## Proof

- Web: `apps/web/src/lib/imageCompression.test.ts` has new tests:
  - A 3024×1964 PNG under the byte cap becomes a 2000×1299 PNG, decoded with `imageOrientation: "from-image"` and with no matte.
  - A JPEG stored landscape with EXIF rotation becomes a 1500×2000 JPEG.
  - A 2000px PNG and a 3000px GIF pass through without being decoded.
  - The two scaling tests fail against `main`'s `imageCompression.ts` and pass with this change.
- Mobile: `apps/mobile/src/lib/composerImages.test.ts` has new tests:
  - A 3024×1964 clipboard PNG is rendered to 2000×1299 and saved as PNG.
  - A 2000px clipboard image is kept byte for byte.
  - The scaling test fails with the fit call removed.
- Targeted tests pass: web 38, mobile composer and sharing 64. Typecheck passes for web, mobile, and client-runtime. Lint and format pass on the changed files.
- No browser or device run was made, and there is no screen capture. All web and mobile tests stub the canvas or the native renderer.

Reviewed with sol-loop: 1 round with GPT-6.1 Sol. Approved. One nit was declined: a scaled PNG still over 10 MB falls back to the existing WebP or JPEG byte-cap re-encode, which is unchanged behavior.

The diagnosis and repro come from #19353, which fixed this on the server but decoded images on the main thread.

Fixes #13647

Model and harness: Claude Opus 5.5 in Claude Code via T3 Code.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

