-
Notifications
You must be signed in to change notification settings - Fork 57
fix(desktop): patch trpc-electron for Electron 39 compatibility #422
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
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]>
WalkthroughA 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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~15–25 minutes
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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:
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-electronThis creates a
patches/directory with readable diffs that are more resilient to updates.Upstream contribution: Submit a PR to trpc-electron that makes the Symbol.asyncDispose assignment conditional or overwritable, benefiting the entire community.
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
📒 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 insrc/lib/trpc
Use alias as defined intsconfig.jsonwhen 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.tsapps/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", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -name "package.json" -path "*/apps/desktop/*" | head -5Repository: 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 fRepository: 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 fRepository: 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
fiRepository: superset-sh/superset
Length of output: 133
🏁 Script executed:
cat apps/desktop/package.json | jq '.scripts.postinstall, .dependencies."trpc-electron", .devDependencies."trpc-electron"' | head -20Repository: superset-sh/superset
Length of output: 118
🏁 Script executed:
cat scripts/patch-trpc-electron.ts | head -100Repository: superset-sh/superset
Length of output: 126
🏁 Script executed:
cat apps/desktop/scripts/patch-trpc-electron.tsRepository: superset-sh/superset
Length of output: 3076
🏁 Script executed:
cat ./apps/desktop/scripts/patch-trpc-electron.tsRepository: superset-sh/superset
Length of output: 3076
🏁 Script executed:
head -150 apps/desktop/scripts/patch-trpc-electron.tsRepository: 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.
| // 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"); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| // 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 | |||
There was a problem hiding this comment.
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 :/
Summary
Symbol.asyncDispose already existserror that breaks tRPC subscriptionsBackground
Electron 39 uses Node.js 22, which has
Symbol.asyncDisposebuilt-in on AsyncIterators. The trpc-electron library throws an error when trying to addSymbol.asyncDisposeto objects that already have it:This was breaking the notification subscription that powers the red dot indicator on workspace tabs.
Changes
apps/desktop/scripts/patch-trpc-electron.ts- patches the library on installapps/desktop/package.json- added postinstall scriptTest plan
bun installin apps/desktop and verify patch appliesbun run dev🤖 Generated with Claude Code
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.