mirror of
https://github.com/stablyai/orca
synced 2026-04-21 14:17:16 +00:00
fix: gracefully handle EPERM during PTY overlay creation on Windows
Filesystem operations in buildPtyEnv (rmSync, mkdirSync, symlinkSync, writeFileSync) can throw EPERM/EBUSY on Windows when antivirus, file indexers, or a previous Orca session hold locks on the overlay directory. Since these operations had no error handling, the entire pty:spawn IPC call would fail, making all terminals unusable. Wrap overlay creation in try/catch for both PiTitlebarExtensionService and OpenCodeHookService so they degrade gracefully — the terminal still spawns, just without the non-critical titlebar spinner or status plugin. Closes #707
This commit is contained in:
parent
fcbdd95b43
commit
04bc2cd5d9
2 changed files with 56 additions and 14 deletions
|
|
@ -208,6 +208,16 @@ export class OpenCodeHookService {
|
|||
}
|
||||
|
||||
const configDir = this.writePluginConfig(ptyId)
|
||||
if (!configDir) {
|
||||
// Why: plugin config is best-effort — return hook vars without
|
||||
// OPENCODE_CONFIG_DIR so the PTY still spawns and manually launched
|
||||
// opencode sessions can still report status via the hook server.
|
||||
return {
|
||||
ORCA_OPENCODE_HOOK_PORT: String(this.port),
|
||||
ORCA_OPENCODE_HOOK_TOKEN: this.token,
|
||||
ORCA_OPENCODE_PTY_ID: ptyId
|
||||
}
|
||||
}
|
||||
|
||||
// Why: OpenCode only reads the extra plugin directory at process startup.
|
||||
// Inject these vars into every Orca PTY so manually launched `opencode`
|
||||
|
|
@ -221,11 +231,18 @@ export class OpenCodeHookService {
|
|||
}
|
||||
}
|
||||
|
||||
private writePluginConfig(ptyId: string): string {
|
||||
private writePluginConfig(ptyId: string): string | null {
|
||||
const configDir = join(app.getPath('userData'), 'opencode-hooks', ptyId)
|
||||
const pluginsDir = join(configDir, 'plugins')
|
||||
mkdirSync(pluginsDir, { recursive: true })
|
||||
writeFileSync(join(pluginsDir, ORCA_OPENCODE_PLUGIN_FILE), getOpenCodePluginSource())
|
||||
try {
|
||||
mkdirSync(pluginsDir, { recursive: true })
|
||||
writeFileSync(join(pluginsDir, ORCA_OPENCODE_PLUGIN_FILE), getOpenCodePluginSource())
|
||||
} catch {
|
||||
// Why: on Windows, userData directories can be locked by antivirus or
|
||||
// indexers (EPERM/EBUSY). Plugin config is non-critical — the PTY should
|
||||
// still spawn without the OpenCode status plugin.
|
||||
return null
|
||||
}
|
||||
return configDir
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,17 +142,36 @@ export class PiTitlebarExtensionService {
|
|||
const sourceAgentDir = existingAgentDir || getDefaultPiAgentDir()
|
||||
const overlayDir = this.getOverlayDir(ptyId)
|
||||
|
||||
rmSync(overlayDir, { recursive: true, force: true })
|
||||
mkdirSync(overlayDir, { recursive: true })
|
||||
this.mirrorAgentDir(sourceAgentDir, overlayDir)
|
||||
try {
|
||||
rmSync(overlayDir, { recursive: true, force: true })
|
||||
} catch {
|
||||
// Why: on Windows the overlay directory can be locked by another process
|
||||
// (e.g. antivirus, indexer, or a previous Orca session that didn't clean up).
|
||||
// rmSync with force:true handles ENOENT but not EPERM/EBUSY. If we can't
|
||||
// remove the stale overlay, fall back to the user's own Pi agent dir so the
|
||||
// terminal still spawns — the titlebar spinner is not worth blocking the PTY.
|
||||
return existingAgentDir ? { PI_CODING_AGENT_DIR: existingAgentDir } : {}
|
||||
}
|
||||
|
||||
const extensionsDir = join(overlayDir, 'extensions')
|
||||
mkdirSync(extensionsDir, { recursive: true })
|
||||
// Why: Pi auto-loads global extensions from PI_CODING_AGENT_DIR/extensions.
|
||||
// Add Orca's titlebar extension alongside the user's existing extensions
|
||||
// instead of replacing that directory, otherwise Orca terminals would
|
||||
// silently disable the user's Pi customization inside Orca only.
|
||||
writeFileSync(join(extensionsDir, ORCA_PI_EXTENSION_FILE), getPiTitlebarExtensionSource())
|
||||
try {
|
||||
mkdirSync(overlayDir, { recursive: true })
|
||||
this.mirrorAgentDir(sourceAgentDir, overlayDir)
|
||||
|
||||
const extensionsDir = join(overlayDir, 'extensions')
|
||||
mkdirSync(extensionsDir, { recursive: true })
|
||||
// Why: Pi auto-loads global extensions from PI_CODING_AGENT_DIR/extensions.
|
||||
// Add Orca's titlebar extension alongside the user's existing extensions
|
||||
// instead of replacing that directory, otherwise Orca terminals would
|
||||
// silently disable the user's Pi customization inside Orca only.
|
||||
writeFileSync(join(extensionsDir, ORCA_PI_EXTENSION_FILE), getPiTitlebarExtensionSource())
|
||||
} catch {
|
||||
// Why: overlay creation is best-effort — permission errors (EPERM/EACCES)
|
||||
// on Windows can occur when the userData directory is restricted or when
|
||||
// symlink/junction creation fails without developer mode. Fall back to the
|
||||
// user's Pi agent dir so the terminal spawns without the Orca extension.
|
||||
this.clearPty(ptyId)
|
||||
return existingAgentDir ? { PI_CODING_AGENT_DIR: existingAgentDir } : {}
|
||||
}
|
||||
|
||||
return {
|
||||
PI_CODING_AGENT_DIR: overlayDir
|
||||
|
|
@ -160,7 +179,13 @@ export class PiTitlebarExtensionService {
|
|||
}
|
||||
|
||||
clearPty(ptyId: string): void {
|
||||
rmSync(this.getOverlayDir(ptyId), { recursive: true, force: true })
|
||||
try {
|
||||
rmSync(this.getOverlayDir(ptyId), { recursive: true, force: true })
|
||||
} catch {
|
||||
// Why: on Windows the overlay dir can be locked (EPERM/EBUSY) by antivirus
|
||||
// or indexers. Overlay cleanup is best-effort — a stale directory in userData
|
||||
// is harmless and will be overwritten on the next PTY spawn attempt.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue