fix(session-notification): revert PR #543 and add proper notification plugin conflict detection (#575)
* revert: undo PR #543 changes (bun shell GC crash was misdiagnosed) This reverts commit4a38e70(PR #543) and2064568(follow-up fix). ## Why This Revert The original diagnosis was incorrect. PR #543 assumed Bun's ShellInterpreter GC bug was causing Windows crashes, but further investigation revealed the actual root cause: **The crash occurs when oh-my-opencode's session-notification runs alongside external notification plugins (e.g., @mohak34/opencode-notifier).** Evidence: - User removed opencode-notifier plugin → crashes stopped - Release version (with original ctx.$ code) works fine when used alone - No widespread crash reports from users without external notifiers - Both plugins listen to session.idle and send concurrent notifications The real issue is a conflict between two notification systems: 1. oh-my-opencode: ctx.$ → PowerShell → Windows.UI.Notifications 2. opencode-notifier: node-notifier → SnoreToast.exe A proper fix will detect and handle this conflict gracefully. Refs: #543, oven-sh/bun#23177, oven-sh/bun#24368 See: docs/CRASH_INVESTIGATION_TIMELINE.md (in follow-up commit) * fix(session-notification): detect and avoid conflict with external notification plugins When oh-my-opencode's session-notification runs alongside external notification plugins like opencode-notifier, both listen to session.idle and send concurrent notifications. This can cause crashes on Windows due to resource contention between different notification mechanisms: - oh-my-opencode: ctx.$ → PowerShell → Windows.UI.Notifications - opencode-notifier: node-notifier → SnoreToast.exe This commit adds: 1. External plugin detection (checks opencode.json for known notifiers) 2. Auto-disable of session-notification when conflict detected 3. Console warning explaining the situation 4. Config option 'notification.force_enable' to override Known notification plugins detected: - opencode-notifier - @mohak34/opencode-notifier - mohak34/opencode-notifier This is the actual fix for the Windows crash issue previously misdiagnosed as a Bun.spawn GC bug (PR #543). Refs: #543 * docs: add crash investigation timeline explaining the real root cause Documents the investigation journey from initial misdiagnosis (Bun GC bug) to discovering the actual root cause (notification plugin conflict). Key findings: - PR #543 was based on incorrect assumption - The real issue is concurrent notification plugins - oh-my-opencode + opencode-notifier = crash on Windows - Either plugin alone works fine * fix: address review feedback - add PowerShell escaping and use existing JSONC parser - Add back single-quote escaping for PowerShell soundPath to prevent command failures - Replace custom stripJsonComments with existing parseJsoncSafe from jsonc-parser - All 655 tests pass --------- Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
b7588ee60e
commit
4902983398
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Detects external plugins that may conflict with oh-my-opencode features.
|
||||
* Used to prevent crashes from concurrent notification plugins.
|
||||
*/
|
||||
|
||||
import * as fs from "node:fs"
|
||||
import * as path from "node:path"
|
||||
import * as os from "node:os"
|
||||
import { log } from "./logger"
|
||||
import { parseJsoncSafe } from "./jsonc-parser"
|
||||
|
||||
interface OpencodeConfig {
|
||||
plugin?: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Known notification plugins that conflict with oh-my-opencode's session-notification.
|
||||
* Both plugins listen to session.idle and send notifications simultaneously,
|
||||
* which can cause crashes on Windows due to resource contention.
|
||||
*/
|
||||
const KNOWN_NOTIFICATION_PLUGINS = [
|
||||
"opencode-notifier",
|
||||
"@mohak34/opencode-notifier",
|
||||
"mohak34/opencode-notifier",
|
||||
]
|
||||
|
||||
function getWindowsAppdataDir(): string | null {
|
||||
return process.env.APPDATA || null
|
||||
}
|
||||
|
||||
function getConfigPaths(directory: string): string[] {
|
||||
const crossPlatformDir = path.join(os.homedir(), ".config")
|
||||
const paths = [
|
||||
path.join(directory, ".opencode", "opencode.json"),
|
||||
path.join(directory, ".opencode", "opencode.jsonc"),
|
||||
path.join(crossPlatformDir, "opencode", "opencode.json"),
|
||||
path.join(crossPlatformDir, "opencode", "opencode.jsonc"),
|
||||
]
|
||||
|
||||
if (process.platform === "win32") {
|
||||
const appdataDir = getWindowsAppdataDir()
|
||||
if (appdataDir) {
|
||||
paths.push(path.join(appdataDir, "opencode", "opencode.json"))
|
||||
paths.push(path.join(appdataDir, "opencode", "opencode.jsonc"))
|
||||
}
|
||||
}
|
||||
|
||||
return paths
|
||||
}
|
||||
|
||||
function loadOpencodePlugins(directory: string): string[] {
|
||||
for (const configPath of getConfigPaths(directory)) {
|
||||
try {
|
||||
if (!fs.existsSync(configPath)) continue
|
||||
const content = fs.readFileSync(configPath, "utf-8")
|
||||
const result = parseJsoncSafe<OpencodeConfig>(content)
|
||||
if (result.data) {
|
||||
return result.data.plugin ?? []
|
||||
}
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a plugin entry matches a known notification plugin.
|
||||
* Handles various formats: "name", "name@version", "npm:name", "file://path/name"
|
||||
*/
|
||||
function matchesNotificationPlugin(entry: string): string | null {
|
||||
const normalized = entry.toLowerCase()
|
||||
for (const known of KNOWN_NOTIFICATION_PLUGINS) {
|
||||
if (
|
||||
normalized === known ||
|
||||
normalized.startsWith(`${known}@`) ||
|
||||
normalized.includes(`/${known}`) ||
|
||||
normalized.endsWith(`/${known}`)
|
||||
) {
|
||||
return known
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export interface ExternalNotifierResult {
|
||||
detected: boolean
|
||||
pluginName: string | null
|
||||
allPlugins: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if any external notification plugin is configured.
|
||||
* Returns information about detected plugins for logging/warning.
|
||||
*/
|
||||
export function detectExternalNotificationPlugin(directory: string): ExternalNotifierResult {
|
||||
const plugins = loadOpencodePlugins(directory)
|
||||
|
||||
for (const plugin of plugins) {
|
||||
const match = matchesNotificationPlugin(plugin)
|
||||
if (match) {
|
||||
log(`Detected external notification plugin: ${plugin}`)
|
||||
return {
|
||||
detected: true,
|
||||
pluginName: match,
|
||||
allPlugins: plugins,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
detected: false,
|
||||
pluginName: null,
|
||||
allPlugins: plugins,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a warning message for users with conflicting notification plugins.
|
||||
*/
|
||||
export function getNotificationConflictWarning(pluginName: string): string {
|
||||
return `[oh-my-opencode] External notification plugin detected: ${pluginName}
|
||||
|
||||
⚠️ Both oh-my-opencode and ${pluginName} listen to session.idle events.
|
||||
Running both simultaneously can cause crashes on Windows.
|
||||
|
||||
oh-my-opencode's session-notification has been auto-disabled.
|
||||
|
||||
To use oh-my-opencode's notifications instead, either:
|
||||
1. Remove ${pluginName} from your opencode.json plugins
|
||||
2. Or set "notification": { "force_enable": true } in oh-my-opencode.json`
|
||||
}
|
||||
Reference in New Issue
Block a user