Skip to content

Conversation

ericallam
Copy link
Member

This also fixes an issue with replaying of runs that include
non-circular references

Copy link

changeset-bot bot commented Sep 15, 2025

⚠️ No Changeset found

Latest commit: ff7be34

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

coderabbitai bot commented Sep 15, 2025

Walkthrough

  • apps/webapp route: Moves the call to prettyPrintPacket into a local payload variable and returns that variable in the typedjson response; behavior unchanged.
  • packages/core ioSerialization: Adds dataType: string to IOPacket; introduces hasCircularReference and isCircularPattern to inspect SuperJSON metadata and a cloneReferences option to control cloning behavior; updates prettyPrintPacket to handle application/json and application/super+json with circular-reference-aware serialization (including a JSON.stringify try/catch fallback) and adjusts replacer logic for cloned circular values. Imports and helper wiring adjusted.
  • tests: Adds tests for prettyPrintPacket (undefined, raw string, JSON string/object, SuperJSON, circular references, filtered keys, BigInt/RegExp/Set/Map, nested structures) and relaxes timing assertions in fairDequeuingStrategy.test.ts.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description is a single-line note and does not follow the repository's required template; it omits the "Closes #" line, the checklist, a Testing section with reproduction steps and results, a Changelog entry, and any screenshots or logs. Because the template is provided and these sections are expected for reviewer verification, the current description is incomplete and insufficient for merging. This lack of required information prevents reviewers from easily validating the fix and test coverage. Please update the PR description to use the repository template: add "Closes #" if applicable, complete the checklist, and confirm the PR title follows convention; include a detailed Testing section with exact steps you ran and which tests passed plus any logs or screenshots that demonstrate the replay bug is fixed; and add a short Changelog entry summarizing the behavioral change so release notes capture it.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title describes the core behavioral change (prettyPrintPacket switching to structured cloning instead of emitting "[Circular]") and aligns with the changes in ioSerialization and tests, so it is on-topic and captures the main intent of the PR. However it contains a small typo in the function name ("prettyPrintingPacket" vs the actual prettyPrintPacket) and is a bit verbose, which may reduce clarity when scanning history. These issues are minor and do not make the title misleading about the main change.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch circular-reference-replay-fix

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/core/src/v3/utils/ioSerialization.ts (3)

446-449: Consider the performance and memory implications of structuredClone.

Using structuredClone() for non-circular references is a good approach, but it may have performance implications for large objects. Consider documenting this behavior change in release notes as it could affect memory usage for deeply nested structures.

Consider adding a depth limit or size check before using structuredClone() for very large objects:

 if (options?.cloneReferences) {
+  // Avoid cloning extremely large objects that might cause memory issues
+  try {
     return structuredClone(value);
+  } catch (e) {
+    // Fall back to [Circular] if cloning fails
+    return "[Circular]";
+  }
 }

638-665: Consider edge cases in circular pattern detection.

The circular pattern detection logic is good but might miss some edge cases. For example, it assumes that circular references always have the same last segment, which might not always be true for complex object graphs.

Consider enhancing the circular detection to handle more complex patterns:

 function isCircularPattern(targetPath: string, referencePath: string): boolean {
   const targetParts = targetPath.split(".");
   const refParts = referencePath.split(".");

   // For circular references, the reference path often contains the target path as a prefix
   // Example: targetPath="user", referencePath="user.details.user"
   // This means user.details.user points back to user (circular)

   // Check if reference path starts with target path + additional segments that loop back
   if (refParts.length > targetParts.length) {
     // Check if reference path starts with target path
     let isPrefix = true;
     for (let i = 0; i < targetParts.length; i++) {
       if (targetParts[i] !== refParts[i]) {
         isPrefix = false;
         break;
       }
     }

     // If reference path starts with target path and ends with target path,
     // it's likely a circular reference (e.g., "user" -> "user.details.user")
     if (isPrefix && refParts[refParts.length - 1] === targetParts[targetParts.length - 1]) {
       return true;
     }
   }
+  
+  // Also check if the reference path IS the target path (direct circular reference)
+  if (targetPath === referencePath) {
+    return true;
+  }

   return false;
 }

395-407: Add an explicit SuperJSON type-guard before accessing rawData.meta

rawData && rawData.meta avoids crashes but doesn't verify the SuperJSON shape — add a small isSuperJSON(rawData) predicate (ensure rawData is an object, rawData.meta is an object, and expected keys like referentialEqualities exist) and use it to decide cloneReferences.
Location: packages/core/src/v3/utils/ioSerialization.ts:395-407.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d333e5 and 55d5a09.

⛔ Files ignored due to path filters (1)
  • references/hello-world/src/trigger/circularPayload.ts is excluded by !references/**
📒 Files selected for processing (3)
  • apps/webapp/app/routes/resources.taskruns.$runParam.replay.ts (2 hunks)
  • packages/core/src/v3/utils/ioSerialization.ts (5 hunks)
  • packages/core/test/ioSerialization.test.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • packages/core/test/ioSerialization.test.ts
  • apps/webapp/app/routes/resources.taskruns.$runParam.replay.ts
  • packages/core/src/v3/utils/ioSerialization.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Our tests are all vitest

Files:

  • packages/core/test/ioSerialization.test.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

We use zod a lot in packages/core and in the webapp

Files:

  • packages/core/test/ioSerialization.test.ts
  • apps/webapp/app/routes/resources.taskruns.$runParam.replay.ts
  • packages/core/src/v3/utils/ioSerialization.ts
**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{test,spec}.{ts,tsx,js,jsx}: Unit tests must use Vitest
Tests should avoid mocks or stubs and use helpers from @internal/testcontainers when Redis or Postgres are needed
Test files live beside the files under test and should use descriptive describe and it blocks

Files:

  • packages/core/test/ioSerialization.test.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

When importing from @trigger.dev/core in the webapp, never import the root package path; always use one of the documented subpath exports from @trigger.dev/core’s package.json

Files:

  • apps/webapp/app/routes/resources.taskruns.$runParam.replay.ts
{apps/webapp/app/**/*.server.{ts,tsx},apps/webapp/app/routes/**/*.ts}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access environment variables only via the env export from app/env.server.ts; do not reference process.env directly

Files:

  • apps/webapp/app/routes/resources.taskruns.$runParam.replay.ts
apps/webapp/app/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Modules intended for test consumption under apps/webapp/app/**/*.ts must not read environment variables; accept configuration via options instead

Files:

  • apps/webapp/app/routes/resources.taskruns.$runParam.replay.ts
🧬 Code graph analysis (2)
packages/core/test/ioSerialization.test.ts (1)
packages/core/src/v3/utils/ioSerialization.ts (1)
  • prettyPrintPacket (379-433)
apps/webapp/app/routes/resources.taskruns.$runParam.replay.ts (2)
packages/core/src/v3/utils/ioSerialization.ts (1)
  • prettyPrintPacket (379-433)
packages/core/src/v3/index.ts (1)
  • prettyPrintPacket (66-66)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
apps/webapp/app/routes/resources.taskruns.$runParam.replay.ts (1)

111-111: Extract prettyPrintPacket() outside the object literal for clarity.

Good refactoring choice. Moving the prettyPrintPacket call to a separate variable makes the code more readable and ensures single evaluation of the async function before returning the response.

packages/core/test/ioSerialization.test.ts (1)

192-346: Comprehensive test coverage for prettyPrintPacket functionality.

Well-structured test suite covering all major scenarios including undefined input, string data, JSON/SuperJSON handling, circular references, filtered keys, and special types (BigInt, RegExp, Set, Map). The tests properly validate the public API changes and the new circular reference handling behavior.

packages/core/src/v3/utils/ioSerialization.ts (2)

415-425: Smart fallback for circular reference handling errors.

Excellent defensive programming! The try-catch with cloneReferences fallback ensures graceful handling of edge cases where circular reference detection might be incomplete or incorrect. This prevents runtime errors while still attempting to provide useful output.


590-665: Well-implemented circular reference detection logic.

The hasCircularReference() and isCircularPattern() functions correctly handle all three SuperJSON referential equality formats. The implementation is thorough and includes detailed comments explaining the different cases.

…circular references instead of outputting [Circular]

This also fixes an issue with replaying of runs that include 
non-circular references
@ericallam ericallam force-pushed the circular-reference-replay-fix branch from 55d5a09 to ff7be34 Compare September 15, 2025 15:51
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
apps/webapp/test/fairDequeuingStrategy.test.ts (1)

273-275: Use tolerance to reduce flakiness for timing assertion.

Direct less‑than comparisons on timing can be noisy. Consider allowing a small tolerance like you do elsewhere.

Apply:

-      // Make sure the second call is faster than the first
-      expect(distribute2Duration).toBeLessThan(distribute1Duration);
+      // Make sure the second call is faster than the first (within tolerance)
+      expect(distribute2Duration).toBeLessThan(withTolerance(distribute1Duration));
packages/core/test/ioSerialization.test.ts (2)

254-267: Strengthen non‑circular reference test: assert absence of “[Circular]”.

This guarantees the new clone‑on‑shared‑refs path is exercised.

Apply:

       const result = await prettyPrintPacket(serialized, "application/super+json");
 
       expect(result).toContain('"person1": {');
       expect(result).toContain('"person2": {');
+      expect(result).not.toContain("[Circular]");

218-238: Avoid brittle whitespace assertions.

String contains with exact indentation can be fragile. Prefer parsing the output and asserting structure (e.g., JSON.parse for JSON cases) or using regex without whitespace coupling.

packages/core/src/v3/utils/ioSerialization.ts (2)

388-406: Guard against invalid SuperJSON input before deserialize.

safeJsonParse can return undefined; calling deserialize on undefined will throw. Early‑return the raw string to keep prettyPrintPacket resilient.

Apply:

   if (dataType === "application/super+json") {
-    if (typeof rawData === "string") {
-      rawData = safeJsonParse(rawData);
-    }
+    if (typeof rawData === "string") {
+      const parsed = safeJsonParse(rawData);
+      if (!parsed) return rawData; // invalid JSON; return as-is
+      rawData = parsed;
+    }
 
     const { deserialize } = await loadSuperJSON();
 
     const hasCircularReferences = rawData && rawData.meta && hasCircularReference(rawData.meta);

446-449: structuredClone may be unavailable or may throw; add safe fallback.

Older runtimes or uncloneable values can cause failures. Fall back to a best‑effort deep copy, then to “[Circular]” if needed.

Apply:

-        if (options?.cloneReferences) {
-          return structuredClone(value);
-        }
+        if (options?.cloneReferences) {
+          try {
+            // Prefer native clone when available
+            // @ts-ignore - guard at runtime
+            if (typeof structuredClone === "function") return structuredClone(value);
+            // Fallback: JSON round‑trip (loses non‑JSON types but avoids crash)
+            return JSON.parse(JSON.stringify(value));
+          } catch {
+            // Last resort: don’t explode, mark as circular
+            return "[Circular]";
+          }
+        }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 55d5a09 and ff7be34.

⛔ Files ignored due to path filters (1)
  • references/hello-world/src/trigger/circularPayload.ts is excluded by !references/**
📒 Files selected for processing (4)
  • apps/webapp/app/routes/resources.taskruns.$runParam.replay.ts (2 hunks)
  • apps/webapp/test/fairDequeuingStrategy.test.ts (2 hunks)
  • packages/core/src/v3/utils/ioSerialization.ts (5 hunks)
  • packages/core/test/ioSerialization.test.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/webapp/app/routes/resources.taskruns.$runParam.replay.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • apps/webapp/test/fairDequeuingStrategy.test.ts
  • packages/core/test/ioSerialization.test.ts
  • packages/core/src/v3/utils/ioSerialization.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Our tests are all vitest

Files:

  • apps/webapp/test/fairDequeuingStrategy.test.ts
  • packages/core/test/ioSerialization.test.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

We use zod a lot in packages/core and in the webapp

Files:

  • apps/webapp/test/fairDequeuingStrategy.test.ts
  • packages/core/test/ioSerialization.test.ts
  • packages/core/src/v3/utils/ioSerialization.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

When importing from @trigger.dev/core in the webapp, never import the root package path; always use one of the documented subpath exports from @trigger.dev/core’s package.json

Files:

  • apps/webapp/test/fairDequeuingStrategy.test.ts
{apps/webapp/**/__tests__/**/*.{ts,tsx},apps/webapp/**/*.{test,spec}.{ts,tsx}}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

{apps/webapp/**/__tests__/**/*.{ts,tsx},apps/webapp/**/*.{test,spec}.{ts,tsx}}: Do not import app/env.server.ts into tests, either directly or indirectly
Tests should only import classes/functions from files under apps/webapp/app/**/*.ts

Files:

  • apps/webapp/test/fairDequeuingStrategy.test.ts
**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{test,spec}.{ts,tsx,js,jsx}: Unit tests must use Vitest
Tests should avoid mocks or stubs and use helpers from @internal/testcontainers when Redis or Postgres are needed
Test files live beside the files under test and should use descriptive describe and it blocks

Files:

  • apps/webapp/test/fairDequeuingStrategy.test.ts
  • packages/core/test/ioSerialization.test.ts
🧬 Code graph analysis (1)
packages/core/test/ioSerialization.test.ts (1)
packages/core/src/v3/utils/ioSerialization.ts (1)
  • prettyPrintPacket (379-433)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (3)
packages/core/test/ioSerialization.test.ts (1)

192-346: Comprehensive coverage of prettyPrintPacket. LGTM.

packages/core/src/v3/utils/ioSerialization.ts (2)

415-425: Good fallback on stringify failure.

Retrying with cloneReferences: false is a sensible safety net if detection ever misclassifies.


601-666: Circular detection heuristics look reasonable; add tests for edge patterns.

The isCircularPattern prefix/suffix check could miss or misclassify complex paths (e.g., sibling cross‑links or deeper ancestor hops). Add cases like target="a.b.c", ref="a.b.c.d.a.b" and non‑circular shared refs across branches.

I can draft additional unit tests covering tricky referentialEqualities shapes if helpful.

@ericallam ericallam merged commit 0f9b83d into main Sep 15, 2025
31 checks passed
@ericallam ericallam deleted the circular-reference-replay-fix branch September 15, 2025 16:43
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.

2 participants