Skip to content

Conversation

@AviPeltz
Copy link
Collaborator

@AviPeltz AviPeltz commented Dec 18, 2025

Summary

  • Adds a postinstall script to patch trpc-electron for Electron 39+ / Node.js 22+ compatibility
  • Fixes the Symbol.asyncDispose already exists error that breaks tRPC subscriptions
  • This enables the visual indicator on workspace tabs to work correctly when agent notifications arrive

Background

Electron 39 uses Node.js 22, which has Symbol.asyncDispose built-in on AsyncIterators. The trpc-electron library throws an error when trying to add Symbol.asyncDispose to objects that already have it:

TRPCClientError: Symbol.asyncDispose already exists

This was breaking the notification subscription that powers the red dot indicator on workspace tabs.

Changes

  • Added apps/desktop/scripts/patch-trpc-electron.ts - patches the library on install
  • Modified apps/desktop/package.json - added postinstall script

Test plan

  • Run bun install in apps/desktop and verify patch applies
  • Start dev server with bun run dev
  • Trigger a Claude agent completion notification
  • Verify the red dot indicator appears on the workspace tab

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores
    • Automated patching of TRPC Electron during installation for compatibility with Electron 39+ and Node.js 22+.

✏️ Tip: You can customize this high-level summary in your review settings.

Electron 39 uses Node.js 22, which has Symbol.asyncDispose built-in on
AsyncIterators. The trpc-electron library throws an error when it tries
to add Symbol.asyncDispose to objects that already have it.

This adds a postinstall script that patches trpc-electron to allow
overwriting Symbol.asyncDispose instead of throwing an error.

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

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@coderabbitai
Copy link

coderabbitai bot commented Dec 18, 2025

Walkthrough

A postinstall script was added to automatically patch the trpc-electron dependency for compatibility with Electron 39+ and Node.js 22+ by removing Symbol.asyncDispose existence checks across source and distribution files.

Changes

Cohort / File(s) Change Summary
Package.json configuration
apps/desktop/package.json
Added postinstall script entry to execute bun run scripts/patch-trpc-electron.ts after dependency installation
Electron TRPC patching script
apps/desktop/scripts/patch-trpc-electron.ts
New script that locates and patches trpc-electron in node_modules, modifying Symbol.asyncDispose checks in src/main/utils.ts, dist/main.mjs, and dist/main.cjs to replace existence checks with overwrite assignments

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~15–25 minutes

  • Verify the file path construction and node_modules resolution logic is robust across environments
  • Confirm the string replacement patterns correctly target Symbol.asyncDispose checks in all three file formats (source, ESM dist, CommonJS minified)
  • Validate error handling behavior when target package or files are missing

Poem

🐰 A patch for the future, applied with care,
No Symbol.asyncDispose hiding there,
Node twenty-two dances, Electron takes flight,
Our postinstall whisper makes everything right! 🚀

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately summarizes the main change: adding a patch for trpc-electron to support Electron 39 compatibility.
Description check ✅ Passed The description covers the required template sections: summary, background, changes, and a test plan. It provides clear context about the issue and solution.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/trpc-electron-async-dispose

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

@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: 2

🧹 Nitpick comments (1)
apps/desktop/scripts/patch-trpc-electron.ts (1)

1-77: Consider more robust alternatives to runtime patching.

While this postinstall patch solves the immediate problem, patching third-party dependencies at install time is fragile and high-maintenance. The brittle regex patterns will break with library updates, and there's no automated way to detect failures.

Consider these alternatives:

  1. patch-package: Industry-standard tool for patching dependencies that creates maintainable diffs:

    # One-time setup
    bun add -D patch-package
    
    # Make changes to node_modules/trpc-electron
    # Then generate patch
    bunx patch-package trpc-electron

    This creates a patches/ directory with readable diffs that are more resilient to updates.

  2. Upstream contribution: Submit a PR to trpc-electron that makes the Symbol.asyncDispose assignment conditional or overwritable, benefiting the entire community.

  3. Fork and publish: If upstream is unmaintained, fork trpc-electron, apply the fix, and use your fork until the issue is resolved upstream.

Any of these approaches would be more maintainable than regex-based patching.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a5e5859 and 4bbea25.

📒 Files selected for processing (2)
  • apps/desktop/package.json (1 hunks)
  • apps/desktop/scripts/patch-trpc-electron.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
apps/desktop/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx,js,jsx}: For Electron interprocess communication, ALWAYS use trpc as defined in src/lib/trpc
Use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.

Files:

  • apps/desktop/scripts/patch-trpc-electron.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Avoid using any type in TypeScript - maintain type safety unless absolutely necessary

Files:

  • apps/desktop/scripts/patch-trpc-electron.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Run Biome for formatting, linting, import organization, and safe fixes at the root level using bun run lint:fix

Files:

  • apps/desktop/scripts/patch-trpc-electron.ts
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-12-18T17:26:38.664Z
Learning: Applies to apps/desktop/**/*.{ts,tsx,js,jsx} : For Electron interprocess communication, ALWAYS use trpc as defined in `src/lib/trpc`
📚 Learning: 2025-12-18T17:26:38.664Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-12-18T17:26:38.664Z
Learning: Applies to apps/desktop/**/*.{ts,tsx,js,jsx} : For Electron interprocess communication, ALWAYS use trpc as defined in `src/lib/trpc`

Applied to files:

  • apps/desktop/scripts/patch-trpc-electron.ts
  • apps/desktop/package.json
📚 Learning: 2025-12-18T23:19:10.405Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-18T23:19:10.405Z
Learning: Applies to apps/desktop/src/{main,renderer,preload}/**/*.{ts,tsx} : Use type-safe IPC communication - define channel types in apps/desktop/src/shared/ipc-channels.ts before implementing handlers

Applied to files:

  • apps/desktop/scripts/patch-trpc-electron.ts
📚 Learning: 2025-12-18T23:19:10.405Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-18T23:19:10.405Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Run Biome for formatting, linting, import organization, and safe fixes at the root level using bun run lint:fix

Applied to files:

  • apps/desktop/package.json
⏰ 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). (1)
  • GitHub Check: Build
🔇 Additional comments (2)
apps/desktop/scripts/patch-trpc-electron.ts (2)

1-10: LGTM!

The documentation clearly explains the compatibility issue with Electron 39+ / Node.js 22+ and the rationale for the patch.


12-21: LGTM!

The package detection logic gracefully handles the case where trpc-electron is not installed, which is appropriate for a postinstall script.

"scripts": {
"clean": "git clean -xdf .cache .turbo dist dist-electron release node_modules",
"start": "electron-vite preview",
"postinstall": "bun run scripts/patch-trpc-electron.ts",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "package.json" -path "*/apps/desktop/*" | head -5

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

cat apps/desktop/package.json | grep -A2 -B2 '"postinstall"'
cat apps/desktop/package.json | grep -A2 -B2 '"trpc-electron"'
find . -name "patch-trpc-electron.ts" -type f

Repository: superset-sh/superset

Length of output: 582


🏁 Script executed:

#!/bin/bash
echo "=== Checking postinstall script ===" 
cat apps/desktop/package.json | grep -A1 '"postinstall"'

echo -e "\n=== Checking trpc-electron version ===" 
cat apps/desktop/package.json | grep -A1 '"trpc-electron"'

echo -e "\n=== Finding patch script ===" 
find . -name "patch-trpc-electron.ts" -type f

Repository: superset-sh/superset

Length of output: 407


🏁 Script executed:

#!/bin/bash
# Find and display the patch script
if [ -f "scripts/patch-trpc-electron.ts" ]; then
  echo "=== Patch script content ===" 
  cat scripts/patch-trpc-electron.ts
else
  echo "Finding patch script..."
  find . -name "*patch*trpc*" -type f 2>/dev/null | head -10
fi

Repository: superset-sh/superset

Length of output: 133


🏁 Script executed:

cat apps/desktop/package.json | jq '.scripts.postinstall, .dependencies."trpc-electron", .devDependencies."trpc-electron"' | head -20

Repository: superset-sh/superset

Length of output: 118


🏁 Script executed:

cat scripts/patch-trpc-electron.ts | head -100

Repository: superset-sh/superset

Length of output: 126


🏁 Script executed:

cat apps/desktop/scripts/patch-trpc-electron.ts

Repository: superset-sh/superset

Length of output: 3076


🏁 Script executed:

cat ./apps/desktop/scripts/patch-trpc-electron.ts

Repository: superset-sh/superset

Length of output: 3076


🏁 Script executed:

head -150 apps/desktop/scripts/patch-trpc-electron.ts

Repository: superset-sh/superset

Length of output: 3076


Pin trpc-electron to an exact version to prevent silent patch failures.

The postinstall script patches trpc-electron using fragile regex patterns that depend on specific code structure and minification artifacts (e.g., function names like Fe, ke). These patterns target three locations: the source TypeScript file, ESM dist, and minified CJS dist. With caret versioning (^0.1.2), any patch/minor release of trpc-electron could change its code structure or minification scheme, causing the patches to silently fail to apply (the script only logs but doesn't error on failed matches). This leaves the app running with an unpatched dependency that will break on Node.js 22+ or Electron 39+.

Change to exact version pinning:

"trpc-electron": "0.1.2"

Revert to caret versioning only after trpc-electron provides an upstream fix for Symbol.asyncDispose compatibility.

🤖 Prompt for AI Agents
In apps/desktop/package.json around line 19, the trpc-electron dependency is
caret-pinned which allows minor/patch upgrades that can change minified output
and cause the fragile postinstall patch script to silently fail; change the
dependency entry to an exact version string "0.1.2" (remove the ^) and update
the project lockfile (bun.lockb / lockfile if present) so installs are
deterministic; keep this exact pin until trpc-electron publishes an upstream
compatibility fix for Symbol.asyncDispose, then revert to caret if desired.

Comment on lines +23 to +39
// Patch the source TypeScript file
const utilsPath = join(trpcElectronPath, "src", "main", "utils.ts");
if (existsSync(utilsPath)) {
let content = readFileSync(utilsPath, "utf-8");
const originalContent = content;

// Remove the check that throws when Symbol.asyncDispose exists
content = content.replace(
/\/\/ eslint-disable-next-line no-restricted-syntax\s*\n\s*if \(it\[Symbol\.asyncDispose\]\) \{\s*\n\s*throw new Error\('Symbol\.asyncDispose already exists'\);\s*\n\s*\}\s*\n/,
"// Node.js 22+ / Electron 39+ has Symbol.asyncDispose built-in on AsyncIterators\n // We overwrite it with our custom dispose function\n"
);

if (content !== originalContent) {
writeFileSync(utilsPath, content);
console.log("Patched trpc-electron/src/main/utils.ts");
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add validation to detect when patches fail to apply.

The regex pattern is extremely brittle and depends on exact formatting, variable names (it), comment text, and error messages. If trpc-electron updates its code, the regex will silently fail to match, and the runtime error will reoccur.

🔎 Suggested improvements

Add validation to ensure the patch was applied or was already present:

 const utilsPath = join(trpcElectronPath, "src", "main", "utils.ts");
 if (existsSync(utilsPath)) {
 	let content = readFileSync(utilsPath, "utf-8");
 	const originalContent = content;
 
+	// Check if already patched
+	const isAlreadyPatched = !content.includes("Symbol.asyncDispose already exists");
+	const needsPatch = content.includes("Symbol.asyncDispose already exists");
+
 	// Remove the check that throws when Symbol.asyncDispose exists
 	content = content.replace(
 		/\/\/ eslint-disable-next-line no-restricted-syntax\s*\n\s*if \(it\[Symbol\.asyncDispose\]\) \{\s*\n\s*throw new Error\('Symbol\.asyncDispose already exists'\);\s*\n\s*\}\s*\n/,
 		"// Node.js 22+ / Electron 39+ has Symbol.asyncDispose built-in on AsyncIterators\n  // We overwrite it with our custom dispose function\n"
 	);
 
 	if (content !== originalContent) {
 		writeFileSync(utilsPath, content);
 		console.log("Patched trpc-electron/src/main/utils.ts");
+	} else if (needsPatch) {
+		console.error("ERROR: Failed to patch trpc-electron/src/main/utils.ts - regex pattern may be outdated");
+		process.exit(1);
 	}
 }

Apply similar validation to the dist file patches (lines 41-75).

📝 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
// Patch the source TypeScript file
const utilsPath = join(trpcElectronPath, "src", "main", "utils.ts");
if (existsSync(utilsPath)) {
let content = readFileSync(utilsPath, "utf-8");
const originalContent = content;
// Remove the check that throws when Symbol.asyncDispose exists
content = content.replace(
/\/\/ eslint-disable-next-line no-restricted-syntax\s*\n\s*if \(it\[Symbol\.asyncDispose\]\) \{\s*\n\s*throw new Error\('Symbol\.asyncDispose already exists'\);\s*\n\s*\}\s*\n/,
"// Node.js 22+ / Electron 39+ has Symbol.asyncDispose built-in on AsyncIterators\n // We overwrite it with our custom dispose function\n"
);
if (content !== originalContent) {
writeFileSync(utilsPath, content);
console.log("Patched trpc-electron/src/main/utils.ts");
}
}
// Patch the source TypeScript file
const utilsPath = join(trpcElectronPath, "src", "main", "utils.ts");
if (existsSync(utilsPath)) {
let content = readFileSync(utilsPath, "utf-8");
const originalContent = content;
// Check if already patched
const isAlreadyPatched = !content.includes("Symbol.asyncDispose already exists");
const needsPatch = content.includes("Symbol.asyncDispose already exists");
// Remove the check that throws when Symbol.asyncDispose exists
content = content.replace(
/\/\/ eslint-disable-next-line no-restricted-syntax\s*\n\s*if \(it\[Symbol\.asyncDispose\]\) \{\s*\n\s*throw new Error\('Symbol\.asyncDispose already exists'\);\s*\n\s*\}\s*\n/,
"// Node.js 22+ / Electron 39+ has Symbol.asyncDispose built-in on AsyncIterators\n // We overwrite it with our custom dispose function\n"
);
if (content !== originalContent) {
writeFileSync(utilsPath, content);
console.log("Patched trpc-electron/src/main/utils.ts");
} else if (needsPatch) {
console.error("ERROR: Failed to patch trpc-electron/src/main/utils.ts - regex pattern may be outdated");
process.exit(1);
}
}
🤖 Prompt for AI Agents
In apps/desktop/scripts/patch-trpc-electron.ts around lines 23 to 39, the
brittle regex replacement may silently fail if upstream formatting or names
change; add explicit validation after attempting the replace: detect whether the
content changed or already contains the intended replacement, and if neither is
true throw or log a failing error and exit (or fail the CI) so the developer is
alerted; apply the same pattern of validation to the subsequent dist file
patches (lines ~41-75) to ensure each patch either applied or was already
present before writing files.

@@ -0,0 +1,77 @@
#!/usr/bin/env bun
Copy link
Collaborator

Choose a reason for hiding this comment

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

we should possibly fork electron-trpc and add it as a package in our repo - it's no longer maintained so we probably will have to do similar stuff to make it work :/

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.

3 participants