Skip to content

feat(images): normalize uploads with Cloudflare Images - #689

Merged
urjitc merged 6 commits into
mainfrom
codex/cloudflare-images
Jul 29, 2026
Merged

feat(images): normalize uploads with Cloudflare Images#689
urjitc merged 6 commits into
mainfrom
codex/cloudflare-images

Conversation

@urjitc

@urjitc urjitc commented Jul 29, 2026

Copy link
Copy Markdown
Member

Summary

  • Replace the dedicated image-converter container with Cloudflare Images transformations.
  • Route chat attachments, workspace HEIC normalization, and Workers AI image extraction through one shared image normalizer.
  • Apply one bounded chat image profile and a 20 MB image transformation limit while retaining the existing 100 MB limit for PDF and Office uploads.

Why

The previous path duplicated image conversion behavior across a container and several callers. A single Cloudflare Images implementation removes that operational surface, keeps transformed bytes out of Durable Object state, and makes size/profile behavior consistent.

Changes

  • Add the IMAGES binding for local, staging, and production environments.
  • Add a streaming image-normalizer with explicit output bounds and content-type handling.
  • Match pending image attachments to the final square preview geometry while keeping non-image cards compact.
  • Remove the ImageFileConverter container, binding, server export, and implementation.
  • Migrate chat attachment intake, workspace upload normalization, and Workers AI extraction.
  • Use one 1024px/70 profile for chat and image extraction, consuming each source stream once.
  • Reject image transformations above 20 MB before conversion.

Testing

  • pnpm exec vitest run src/features/workspaces/conversion/image-normalizer.test.ts src/features/workspaces/upload/workspace-file-upload-storage.test.ts src/features/workspaces/upload/workspace-upload-intake.test.ts — 22 tests passed.
  • pnpm check — all 650 files formatted; no warnings, lint errors, or type errors across 626 checked files.
  • Runtime intake logs supplied during implementation showed successful chat image transformations in 398–630 ms.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Image uploads are normalized to JPEG with improved validation and size controls.
    • Added a 20 MB limit for workspace image uploads.
    • Chat image processing now enforces normalized output limits and clearer error responses.
    • Image attachments display loading placeholders until previews are ready.
  • Bug Fixes

    • Oversized or empty image conversions now return consistent, actionable errors.
    • Image preview and upload handling is more reliable across supported formats.
  • Tests

    • Added coverage for JPEG normalization, size limits, empty outputs, and conversion failures.

@cursor

cursor Bot commented Jul 29, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@capy-ai

capy-ai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Capy auto-review is paused for this organization because the usage-cycle auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews.

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit 6ddc851.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The image-converter container and conversion module are removed. Cloudflare Images now normalizes workspace and chat images into bounded JPEGs, with updated upload validation, storage, AI extraction, error handling, runtime bindings, tests, and loading attachment UI.

Changes

Image normalization migration

Layer / File(s) Summary
Runtime binding and converter removal
containers/image-converter/*, wrangler.jsonc, worker-configuration.d.ts, src/server.ts
The ImageFileConverter container, durable object, implementation, and export are removed; Cloudflare Images bindings and related runtime declarations are added.
Bounded JPEG normalizer and error contract
src/features/workspaces/conversion/image-normalizer.ts, src/features/workspaces/conversion/errors.ts, src/features/workspaces/conversion/image-normalizer.test.ts
Workspace and chat image streams are converted through fixed JPEG profiles, checked for empty or oversized output, and translated into classified conversion errors.
Upload limits and storage conversion
src/features/workspaces/model/workspace-file/limits.ts, src/features/workspaces/upload/*, src/routes/api/v1/workspaces.$workspaceId.file-upload.ts
Image uploads are limited to 20 MB, storage conversion uses the normalizer, and oversized normalized output maps to a 413 response.
AI extraction and chat attachment conversion
src/features/workspaces/extraction/providers/workers-ai-to-markdown.ts, src/routes/api/v1/workspaces.$workspaceId.ai-threads.$threadId.attachments.ts
Workers AI extraction and chat attachment uploads use bounded chat JPEG normalization and record normalized output metadata.
Image attachment loading states
src/features/workspaces/components/ai-chat/AiChatAttachmentItem.tsx
Non-ready image attachments render skeleton placeholders and enable preview controls only after an image URL is available.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately captures the main change: using Cloudflare Images to normalize uploads.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/cloudflare-images

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/features/workspaces/conversion/image-normalizer.ts (1)

139-151: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Preserve the original error for debugging.

translateImageNormalizationErrors re-wraps unknown errors using only error.message, discarding the original stack trace/cause. This makes it harder to distinguish transient Images API failures from genuine content errors when triaging production incidents.

♻️ Suggested fix using ES2022 error `cause`
 		throw new ImageNormalizationError(
 			error instanceof Error ? error.message : "Image normalization failed.",
-		);
+			{ cause: error },
+		);

(Requires ImageNormalizationError/WorkspaceFileConversionError constructors to forward an optional options: ErrorOptions to super().)

🤖 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 `@src/features/workspaces/conversion/image-normalizer.ts` around lines 139 -
151, Update translateImageNormalizationErrors to preserve unknown errors as the
cause when constructing ImageNormalizationError, rather than retaining only
error.message. Ensure the ImageNormalizationError and any relevant
WorkspaceFileConversionError constructors accept and forward optional
ErrorOptions to super(), while leaving existing ImageNormalizationError
instances unchanged.
src/features/workspaces/conversion/image-normalizer.test.ts (1)

9-88: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the empty-output error path.

Tests cover size-limit fallback and generic error translation, but not the case where Images returns a zero-byte/empty body (the createEmptyImageError path in both requireSizedResponseBody and readStreamWithinLimit). This is one of the module's core correctness guarantees.

🤖 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 `@src/features/workspaces/conversion/image-normalizer.test.ts` around lines 9 -
88, Add tests in the “image normalizer” suite for empty Images output, covering
both normalizeImageToJpeg and normalizeChatImageToJpeg so their
requireSizedResponseBody/readStreamWithinLimit paths reject with
ImageNormalizationError. Assert the rejection uses createEmptyImageError
semantics and verifies the relevant Images binding invocation.
🤖 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 `@src/features/workspaces/conversion/image-normalizer.test.ts`:
- Around line 9-88: Add tests in the “image normalizer” suite for empty Images
output, covering both normalizeImageToJpeg and normalizeChatImageToJpeg so their
requireSizedResponseBody/readStreamWithinLimit paths reject with
ImageNormalizationError. Assert the rejection uses createEmptyImageError
semantics and verifies the relevant Images binding invocation.

In `@src/features/workspaces/conversion/image-normalizer.ts`:
- Around line 139-151: Update translateImageNormalizationErrors to preserve
unknown errors as the cause when constructing ImageNormalizationError, rather
than retaining only error.message. Ensure the ImageNormalizationError and any
relevant WorkspaceFileConversionError constructors accept and forward optional
ErrorOptions to super(), while leaving existing ImageNormalizationError
instances unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ef929edd-4ff7-4b91-9d0f-c06619ae4ae0

📥 Commits

Reviewing files that changed from the base of the PR and between 4294796 and 2d3cfe8.

📒 Files selected for processing (17)
  • containers/image-converter/Dockerfile
  • containers/image-converter/server.mjs
  • src/features/workspaces/conversion/image-file-converter.ts
  • src/features/workspaces/conversion/image-normalizer.test.ts
  • src/features/workspaces/conversion/image-normalizer.ts
  • src/features/workspaces/extraction/providers/workers-ai-to-markdown.ts
  • src/features/workspaces/extraction/types.ts
  • src/features/workspaces/extraction/workspace-file-extraction-workflow.ts
  • src/features/workspaces/model/workspace-file/limits.ts
  • src/features/workspaces/upload/workspace-file-upload-storage.test.ts
  • src/features/workspaces/upload/workspace-file-upload-storage.ts
  • src/features/workspaces/upload/workspace-upload-intake.test.ts
  • src/features/workspaces/upload/workspace-upload-intake.ts
  • src/routes/api/v1/workspaces.$workspaceId.ai-threads.$threadId.attachments.ts
  • src/server.ts
  • worker-configuration.d.ts
  • wrangler.jsonc
💤 Files with no reviewable changes (4)
  • containers/image-converter/Dockerfile
  • containers/image-converter/server.mjs
  • src/server.ts
  • src/features/workspaces/conversion/image-file-converter.ts

Comment on lines +72 to +74
throw new ImageNormalizationError(
"Cloudflare Images could not produce a JPEG within the chat attachment limit.",
);

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.

P1 Preserve the attachment size error

When a valid chat image remains over the limit after both profiles, this throws ImageNormalizationError, which the route maps to 422 CONVERSION_FAILED. This replaces the previous 413 ATTACHMENT_TOO_LARGE response and tells users that conversion failed instead of explaining that the optimized image is still too detailed.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Artifacts

Repro: pre-change route output showing the HTTP 413 ATTACHMENT_TOO_LARGE contract

  • The full command output behind this check.

Repro: current route failure output showing both transform attempts and the HTTP 422 CONVERSION_FAILED response

  • The full command output behind this check.

Repro: executable pre-change route contract test

  • Evidence file captured while the check ran.

Repro: executable current route-level failing test with mocked Images transformations

  • Evidence file captured while the check ran.

Repro: Cloudflare workers environment mock used by both route tests

  • Evidence file captured while the check ran.

Repro: Vitest configuration for the pre-change route contract

  • Evidence file captured while the check ran.

Repro: Vitest configuration for the current route reproduction

  • Evidence file captured while the check ran.

View artifacts

T-Rex Ran code and verified through T-Rex

Fix in Cursor

quality: 92,
});

return requireSizedResponseBody(result.response(), createEmptyImageError);

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.

P1 Do not require transformation Content-Length

When a HEIC or HEIF upload uses the real Images binding, result.response() exposes transformed output as a stream without a guaranteed Content-Length, but requireSizedResponseBody rejects any response lacking that header. Valid image uploads therefore fail as empty conversions before the JPEG can be stored in R2; the test masks this by adding the header manually.

Artifacts

Repro: focused executable Vitest harness using a nonempty streamed JPEG response without Content-Length

  • Evidence file captured while the check ran.

Repro: focused Vitest configuration used to execute the harness

  • Evidence file captured while the check ran.

Repro: verbose failing run showing stream characteristics and the ImageNormalizationError stack

  • The full error output from the failing run.

View artifacts

T-Rex Ran code and verified through T-Rex

Fix in Cursor

@greptile-apps

greptile-apps Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces the image-converter container with a shared Cloudflare Images normalization pipeline.

  • Adds bounded chat-image profiles and a 20 MB image intake limit.
  • Migrates workspace HEIC conversion, chat attachment intake, and Workers AI extraction to the new normalizer.
  • Adds repeatable R2 source opening for multi-profile extraction and removes the former container binding, implementation, and Durable Object class.
  • Configures the IMAGES binding across local, staging, and production environments.

Confidence Score: 2/5

This PR should not merge until workspace image normalization handles the Images binding’s streaming output correctly and preserves the attachment size-error contract.

The primary HEIC conversion path can reject valid transformed output while determining its size, and chat images that cannot fit the normalized limit are now incorrectly reported as generic conversion failures.

Files Needing Attention: src/features/workspaces/conversion/image-normalizer.ts and src/routes/api/v1/workspaces.$workspaceId.ai-threads.$threadId.attachments.ts

T-Rex T-Rex Logs

What T-Rex did

  • Reproduced the pre-change route output that returned HTTP 413 ATTACHMENT_TOO_LARGE and, using the current route with mocked Cloudflare Images outputs, observed HTTP 422 CONVERSION_FAILED after attempting the 2048px/85 and 1024px/70 profiles, indicating the route-level contract diverged from the pre-change expectation.
  • Validated the no-Content-Length transformation behavior by running the focused Vitest harness against a nonempty JPEG ReadableStream and confirming the failure path leading to ImageNormalizationError.
  • Ran the focused Vitest suite covering image-normalizer and related upload tests, which completed with exit code 0 in about 1.01 seconds.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
src/features/workspaces/conversion/image-normalizer.ts Adds shared streaming and bounded image normalization, but generic size-exhaustion errors regress the attachment API and unbounded normalization assumes an unavailable response size header.
src/routes/api/v1/workspaces.$workspaceId.ai-threads.$threadId.attachments.ts Migrates chat uploads to the shared normalizer but no longer preserves the size-specific response when normalization cannot meet the attachment cap.
src/features/workspaces/upload/workspace-file-upload-storage.ts Routes HEIC conversion through Cloudflare Images; the orchestration remains coherent but inherits the normalizer’s response-sizing failure.
src/features/workspaces/extraction/workspace-file-extraction-workflow.ts Adds guarded repeatable R2 reads for multi-profile image extraction, validating both size and ETag.
src/features/workspaces/extraction/providers/workers-ai-to-markdown.ts Reuses bounded normalization before passing image bytes to Workers AI.
src/features/workspaces/upload/workspace-upload-intake.ts Enforces the new 20 MB limit for accepted workspace image formats before transformation.
wrangler.jsonc Adds IMAGES bindings and consistently removes the retired image-converter container, namespace binding, and Durable Object class through a deletion migration.

Fix All in Cursor

Reviews (1): Last reviewed commit: "fix(uploads): enforce image transformati..." | Re-trigger Greptile

@cubic-dev-ai cubic-dev-ai Bot 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.

All reported issues were addressed across 17 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/features/workspaces/conversion/image-normalizer.ts Outdated
Comment thread src/features/workspaces/conversion/image-normalizer.ts Outdated
urjitc added 4 commits July 29, 2026 02:46
Use the Images binding byte-stream interface instead of response headers.
Classify output limits for route-specific 413 responses and preserve causes.
Use one bounded 1024px JPEG profile for chat and image extraction.
Consume the existing stream directly and remove the duplicate R2 read contract.
Raise the typed size error where bounded stream reading detects overflow.
Remove the impossible nullable result from both normalization paths.
Render pending images in the same square tile used by the ready preview.
Keep non-image attachment loading states in their compact horizontal layout.
@urjitc
urjitc merged commit 707a49c into main Jul 29, 2026
10 of 11 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Dev Board Jul 29, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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.

Inline comments:
In `@src/features/workspaces/components/ai-chat/AiChatAttachmentItem.tsx`:
- Around line 81-103: Update AiChatAttachmentItem’s ready-image handling so a
ready attachment without a URL cannot remain in the done/loading presentation.
Enforce a defined url in the FileAttachmentData ready contract, or explicitly
map this invalid state to the unavailable/error attachment state and
corresponding UI instead of rendering “Preparing …”.

In `@src/features/workspaces/conversion/image-normalizer.ts`:
- Around line 104-106: Update the overflow branch in the image normalization
flow around reader.cancel to treat cancellation as best-effort: prevent a
rejection from replacing the createImageOutputTooLargeError result, then always
throw the typed output-too-large error. Add a regression test using a stream
whose cancellation rejects and verify normalization still returns the
output_too_large/413 error through translateImageNormalizationErrors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e1dfbafe-a892-4cab-b82b-7a0eb785da72

📥 Commits

Reviewing files that changed from the base of the PR and between 2d3cfe8 and 6ddc851.

📒 Files selected for processing (8)
  • src/features/workspaces/components/ai-chat/AiChatAttachmentItem.tsx
  • src/features/workspaces/conversion/errors.ts
  • src/features/workspaces/conversion/image-normalizer.test.ts
  • src/features/workspaces/conversion/image-normalizer.ts
  • src/features/workspaces/extraction/providers/workers-ai-to-markdown.ts
  • src/features/workspaces/upload/workspace-file-upload-storage.ts
  • src/routes/api/v1/workspaces.$workspaceId.ai-threads.$threadId.attachments.ts
  • src/routes/api/v1/workspaces.$workspaceId.file-upload.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/routes/api/v1/workspaces.$workspaceId.ai-threads.$threadId.attachments.ts
  • src/features/workspaces/extraction/providers/workers-ai-to-markdown.ts
  • src/features/workspaces/upload/workspace-file-upload-storage.ts

Comment on lines +81 to +103
const imageUrl = data.status === "ready" ? data.url : undefined;

return (
<>
<Attachment
className="cursor-zoom-in focus-within:ring-2"
className={imageUrl ? "cursor-zoom-in focus-within:ring-2" : undefined}
orientation="vertical"
size="default"
state={getAttachmentState(data)}
>
<AttachmentMedia variant="image">
<img
alt={label}
className="size-full object-cover"
height={96}
src={data.url}
width={96}
/>
{imageUrl ? (
<img
alt={label}
className="size-full object-cover"
height={96}
src={imageUrl}
width={96}
/>
) : (
<>
<Skeleton aria-hidden="true" className="size-full rounded-none bg-foreground/10" />
<span className="sr-only">Preparing {label}</span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Handle ready images that have no URL.

Line 81 allows the valid FileAttachmentData state { status: "ready", url: undefined }. It is then marked done but permanently renders “Preparing …”. Require url for the ready variant at the attachment contract, or render an unavailable/error state for this invariant violation.

🤖 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 `@src/features/workspaces/components/ai-chat/AiChatAttachmentItem.tsx` around
lines 81 - 103, Update AiChatAttachmentItem’s ready-image handling so a ready
attachment without a URL cannot remain in the done/loading presentation. Enforce
a defined url in the FileAttachmentData ready contract, or explicitly map this
invalid state to the unavailable/error attachment state and corresponding UI
instead of rendering “Preparing …”.

Comment on lines +104 to +106
if (totalBytes + value.byteLength > maxBytes) {
await reader.cancel("Image output exceeds the byte limit.");
throw createImageOutputTooLargeError(maxBytes);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
fd -a 'image-normalizer\.ts$' . || true

echo "== file outline =="
ast-grep outline src/features/workspaces/conversion/image-normalizer.ts --view compact || true

echo "== relevant lines =="
cat -n src/features/workspaces/conversion/image-normalizer.ts | sed -n '1,220p'

echo "== search related errors/usages =="
rg -n "createImageOutputTooLargeError|output_too_large|conversion_failed|reader\.cancel|Image output exceeds" src || true

Repository: ThinkEx-OSS/thinkex

Length of output: 6847


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== errors.ts =="
cat -n src/features/workspaces/conversion/errors.ts

echo "== route usages context =="
cat - n src/routes/api/v1/workspaces.$workspaceId.file-upload.ts | sed -n '330,370p' || true
cat -n src/routes/api/v1/workspaces.$workspaceId.ai-threads.$threadId.attachments.ts | sed -n '80,120p' || true

echo "== normalize tests context =="
cat -n src/features/workspaces/conversion/image-normalizer.test.ts | sed -n '1,180p'

echo "== deterministic JS probe: await reader.cancel(rejects) then throw behavior =="
node - <<'JS'
function translateImageNormalizationErrors(run) {
  return run().catch(error => {
    const createImageNormalizationError = (message, failure, options) => {
      const err = new Error(message);
      err.failure = failure || "conversion_failed";
      err.cause = options?.cause;
      err.name = "ImageNormalizationError";
      return err;
    };
    if (error && error.name === "ImageNormalizationError") throw error;
    throw createImageNormalizationError(
      error instanceof Error ? error.message : "Image normalization failed.",
      "conversion_failed",
      { cause: error }
    );
  });
}

async function currentPath() {
  const reader = {
    cancel(message) {
      return Promise.reject(new Error(message));
    }
  };
  await translateImageNormalizationErrors(async () => {
    await reader.cancel("Image output exceeds the byte limit.");
    throw { name: "ImageNormalizationError", failure: "output_too_large" };
  });
}

currentPath().catch(error => {
  console.log(JSON.stringify({
    name: error.name,
    failure: error.failure,
    causeMessage: error.cause instanceof Error ? error.cause.message : error.cause,
  }));
});
JS

Repository: ThinkEx-OSS/thinkex

Length of output: 7126


Preserve the typed overflow error if cancellation fails.

reader.cancel() is awaited here, so a rejection is caught by translateImageNormalizationErrors and wrapped as conversion_failed instead of the intended output_too_large (413). Cancel best-effort, then throw output_too_large.

Proposed fix
 			if (totalBytes + value.byteLength > maxBytes) {
-				await reader.cancel("Image output exceeds the byte limit.");
+				void reader.cancel("Image output exceeds the byte limit.").catch(() => undefined);
 				throw createImageOutputTooLargeError(maxBytes);
 			}

Add a regression test with a stream whose cancellation rejects.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (totalBytes + value.byteLength > maxBytes) {
await reader.cancel("Image output exceeds the byte limit.");
throw createImageOutputTooLargeError(maxBytes);
if (totalBytes + value.byteLength > maxBytes) {
void reader.cancel("Image output exceeds the byte limit.").catch(() => undefined);
throw createImageOutputTooLargeError(maxBytes);
🤖 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 `@src/features/workspaces/conversion/image-normalizer.ts` around lines 104 -
106, Update the overflow branch in the image normalization flow around
reader.cancel to treat cancellation as best-effort: prevent a rejection from
replacing the createImageOutputTooLargeError result, then always throw the typed
output-too-large error. Add a regression test using a stream whose cancellation
rejects and verify normalization still returns the output_too_large/413 error
through translateImageNormalizationErrors.

@urjitc
urjitc deleted the codex/cloudflare-images branch July 30, 2026 05:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant