fix: address pr-bug-scan validated finding from #7421

Removed removeStaleRuntimeVersions + its call in ensureRelocatedNodePtyNativeRuntime; relocation no longer deletes prior version dirs a surviving daemon still loads conpty from.
This commit is contained in:
orca-bug-scan-bot
2026-07-05 20:41:18 -07:00
parent 509c41e2bf
commit 855cc29493
2 changed files with 15 additions and 37 deletions
@@ -105,7 +105,7 @@ describe('ensureRelocatedNodePtyNativeRuntime', () => {
expect(readFileSync(join(destDir!, 'conpty.node'), 'utf8')).toBe('binding')
})
it('removes stale version dirs but keeps the current one', () => {
it('preserves prior version dirs so a surviving daemon keeps its runtime', () => {
const sourceDir = join(tempDir, 'source')
seedSourceDir(sourceDir)
const destRoot = join(tempDir, 'dest')
@@ -114,7 +114,11 @@ describe('ensureRelocatedNodePtyNativeRuntime', () => {
const destDir = ensureRelocatedNodePtyNativeRuntime({ sourceDir, destRoot, version: '2.0.0' })
expect(destDir).toBe(join(destRoot, '2.0.0'))
expect(existsSync(join(destRoot, '1.0.0'))).toBe(false)
// Why: a daemon adopted from the previous app version still loads
// conpty.dll/OpenConsole.exe from its own version dir; deleting it out
// from under the live daemon breaks its next conpty spawn. The prior
// version dir must survive the new version's relocation.
expect(existsSync(join(destRoot, '1.0.0', 'conpty.node'))).toBe(true)
expect(existsSync(join(destRoot, '2.0.0', 'conpty.node'))).toBe(true)
})
+9 -35
View File
@@ -1,12 +1,4 @@
import {
copyFileSync,
existsSync,
mkdirSync,
readdirSync,
renameSync,
rmSync,
writeFileSync
} from 'node:fs'
import { copyFileSync, existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import { dirname, join } from 'node:path'
import { app } from 'electron'
@@ -55,31 +47,6 @@ function copyRuntimeTree(sourceDir: string, destDir: string): void {
}
}
function removeStaleRuntimeVersions(destRoot: string, keepVersion: string): void {
let entries
try {
entries = readdirSync(destRoot, { withFileTypes: true })
} catch {
return
}
for (const entry of entries) {
if (!entry.isDirectory() || entry.name === keepVersion) {
continue
}
// Why: an adopted daemon from a previous version may still run (or later
// respawn) binaries out of its own version dir. Renaming a directory
// fails on Windows while anything inside is open, so a successful rename
// proves the dir is unused and safe to delete.
const doomedPath = join(destRoot, `${entry.name}.stale`)
try {
renameSync(join(destRoot, entry.name), doomedPath)
rmSync(doomedPath, { recursive: true, force: true })
} catch {
// Still in use (or already being cleaned) — retry on a future launch.
}
}
}
export function ensureRelocatedNodePtyNativeRuntime(options: {
sourceDir: string
destRoot: string
@@ -95,7 +62,14 @@ export function ensureRelocatedNodePtyNativeRuntime(options: {
copyRuntimeTree(sourceDir, destDir)
writeFileSync(join(destDir, RELOCATION_COMPLETE_MARKER), '')
}
removeStaleRuntimeVersions(destRoot, version)
// Why: stale per-version dirs are deliberately NOT deleted here. A
// surviving daemon adopted from a previous app version still loads
// conpty.dll/OpenConsole.exe out of its own version dir; on Windows the
// running .exe/.dll images open with FILE_SHARE_DELETE, so a rename+delete
// would succeed and pull the runtime out from under that live daemon,
// breaking its next conpty spawn (ERROR_PATH_NOT_FOUND). Leaving old dirs
// in place is safe — they are small and only cleaned once it's provably
// safe (see follow-up daemon-version plumbing).
return destDir
} catch {
// Fail open: node-pty keeps loading from the install dir, which is the