Developing
Size
S
Change Breakdown
Bug Fix85%
Refactor15%
#2959Fix Replay Data Corruption for Non-JSON Payloads (#2813)

Non-JSON payloads are being preserved in run replays

Replaying runs with text or raw string payloads will no longer silently corrupt data into empty JSON objects, and small non-JSON payloads are now directly editable in the UI.

Here's the LatestUpdated as code changes

Previously, replaying a run with a non-JSON payload (like plain text) forced the data through a strict JSON parser. When parsing inevitably failed, the system silently defaulted the payload to an empty JSON object, permanently corrupting the replayed data. The UI compounded the issue by strictly assuming any non-JSON payload was a "large payload," completely locking developers out of editing it.

This update removes the aggressive JSON transformation, allowing payloads to pass through the replay pipeline as opaque strings. Replaying a run without modifying its payload now produces an exact, semantically identical input to the original execution. Additionally, the web app's replay dialog intelligently determines editability based on storage semantics rather than MIME types. Payloads are now fully editable in the browser as long as they are not stored via external storage and fall under a safe 512KB limit.

This analysis will evolve. Full story with review threads and final assessment available after merge.
View Original GitHub Description

Summary This PR fixes a data corruption bug in the run replay flow where non-JSON payloads (e.g., text/plain strings) were being mutated into {}. It ensures that payloads are handled as opaque strings throughout the replay path and improves UI editability rules based on storage semantics.

Problems Corrected Empty Payload on Replay: Replaying a run with a non-JSON payload (e.g., text/plain strings) without overrides resulted in data corruption, defaulting the payload to {}. Incorrect UI Label: The Replay modal misclassified non-JSON payloads as "large payloads" and blocked editing. Technical Changes

  1. Payload-Agnostic Replay Schema Modified apps/webapp/app/v3/replayTask.ts to remove an unintended Zod transformation that forced JSON parsing and defaulted missing payloads to {}. The schema now treats payloads as opaque strings passed through without transformation, preserving data integrity when replaying without overrides.

  2. Robust UI Editability Rules Updated apps/webapp/app/components/runs/v3/ReplayRunDialog.tsx to determine editability based on system constraints rather than MIME type assumptions:

Storage-based: Payloads are editable unless stored externally (application/store). Size-limited: Added a 512KB limit (safe threshold for in-browser editing) to prevent accidental rendering of multi-MB payloads. Improved Classification: The "large payload" tooltip and documentation link now only appear for runs that actually use external storage. Verification Data Integrity Invariants Replay Symmetry: Replaying a run without modifying the payload produces a semantically identical run input to the original execution. No-Override Preservation: Verified that replaying a run without specifying a payload override correctly preserves the original payload instead of defaulting to {}. Opaque String Handling: Replaying a string payload that "looks like JSON" (e.g., "{not: 'json'}") is preserved as a string throughout the path, proving no accidental re-parsing occurs. Automated Test Results Validated via reproduction script:

Missing Payload: Preserved as undefined (Correct) String Payload: Accepted verbatim (Correct) Looks-like-JSON: Preserved as string (Correct) prettyPrintPacket : Returns verbatim for text/plain types.

<!-- devin-review-badge-begin -->
<a href="https://app.devin.ai/review/triggerdotdev/trigger.dev/pull/2959"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end -->
© 2026 · via Gitpulse