From cf992b52f96dc33781726b502f6a123d3a1d8d81 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Mon, 14 Sep 2026 22:09:02 +0800 Subject: [PATCH] fix(plugins): preserve observed exit codes on cancellation --- packages/boot/plugin-manager/src/installer.ts | 6 +++--- packages/boot/plugin-manager/tests/plugin-manager.spec.ts | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/boot/plugin-manager/src/installer.ts b/packages/boot/plugin-manager/src/installer.ts index 9a1200daa5..d09aa97325 100644 --- a/packages/boot/plugin-manager/src/installer.ts +++ b/packages/boot/plugin-manager/src/installer.ts @@ -284,7 +284,7 @@ export class PluginInstaller { const deadline = new AbortController() const signal = control === undefined ? deadline.signal : AbortSignal.any([deadline.signal, control.signal]) const timer = setTimeout(() => { deadline.abort() }, config.installTimeoutMs) - let exitCode: number | null + let exitCode: number | null = null try { checkCancelled(control) const child = this.spawn({ @@ -313,9 +313,9 @@ export class PluginInstaller { } catch (error) { const message = messageOf(error) record('stderr', `${message}\n`) - this.options.installLog({ ...request, jobId, argv, cwd: profileDir, spec, stream: 'stderr', text: '', exitCode: null }) + this.options.installLog({ ...request, jobId, argv, cwd: profileDir, spec, stream: 'stderr', text: '', exitCode }) if (error instanceof PluginOperationError && error.code === 'plugins/install-cancelled') throw error - throw new PluginOperationError('plugins/install-failed', `${NAME}: ${message}`, { spec, exitCode: null, log: tail.join('') }, { cause: error }) + throw new PluginOperationError('plugins/install-failed', `${NAME}: ${message}`, { spec, exitCode, log: tail.join('') }, { cause: error }) } finally { clearTimeout(timer) } diff --git a/packages/boot/plugin-manager/tests/plugin-manager.spec.ts b/packages/boot/plugin-manager/tests/plugin-manager.spec.ts index 065f944077..66e35b6686 100644 --- a/packages/boot/plugin-manager/tests/plugin-manager.spec.ts +++ b/packages/boot/plugin-manager/tests/plugin-manager.spec.ts @@ -691,7 +691,7 @@ describe('PluginManager', () => { setInterval(() => {}, 1000); `) const ready = Promise.withResolvers() - const { ctx, manager } = await bootProfile(staged, {}, { + const { ctx, manager, log } = await bootProfile(staged, {}, { pnpmCommand: process.execPath, installTimeoutMs: 30_000, installKillGraceMs: 50, }) ctx.on('plugins/install-log', (chunk) => { @@ -703,6 +703,7 @@ describe('PluginManager', () => { expect(pid).toBeGreaterThan(0) expect(await manager.cancelInstall(requestId)).toEqual({ status: 'cancelled' }) expect(await result).toMatchObject({ code: 'plugins/install-cancelled' }) + expect(log.at(-1)?.exitCode).toBe(0) // A reparented Linux child may remain a zombie until init reaps it; it cannot execute or write. let status = '' try { status = execFileSync('ps', ['-p', String(pid), '-o', 'stat='], { encoding: 'utf8' }).trim() } catch (error) {