fix(cli): exit cleanly after printing shell completions

Force process termination via `postmortem.quit(0)` in
`Completions.run()` after writing completion scripts to stdout. Loading
all command modules during completion generation leaves open event loop
handles (sockets, timers) that prevent natural process exit, causing
tools like chezmoi to hang.
This commit is contained in:
ChangZhuo Chen (陳昌倬)
2026-08-16 14:38:29 +08:00
parent 37eee71978
commit a7dc534598
3 changed files with 27 additions and 1 deletions
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- Fixed `omp completions <shell>` hanging after writing shell completion scripts to stdout by invoking `postmortem.quit(0)` upon completion. Prevents lingering event loop handles (such as background timers or sockets loaded when inspecting command metadata) from pinning the process and blocking tools like `chezmoi`.
## [17.3.5] - 2026-08-16
### Added
@@ -5,7 +5,7 @@
* (see `cli/completion-gen.ts`), so it never drifts from the actual CLI surface.
*/
import { APP_NAME, VERSION } from "@oh-my-pi/pi-utils";
import { APP_NAME, postmortem, VERSION } from "@oh-my-pi/pi-utils";
import { Args, type CliConfig, Command, type CommandCtor } from "@oh-my-pi/pi-utils/cli";
import { completionsHelp as commandHelp } from "../cli/command-help";
import { buildSpec, generateCompletion, type Shell } from "../cli/completion-gen";
@@ -55,6 +55,7 @@ export default class Completions extends Command {
}
await Bun.write(Bun.stdout, await generateLiveCompletion(shell));
await postmortem.quit(0);
}
}
@@ -0,0 +1,21 @@
import { afterEach, describe, expect, it, spyOn } from "bun:test";
import { postmortem } from "@oh-my-pi/pi-utils";
import Completions from "../src/commands/completions";
describe("Completions command exit contract", () => {
afterEach(() => {
spyOn(postmortem, "quit").mockRestore();
spyOn(Bun, "write").mockRestore();
});
it("calls postmortem.quit(0) after writing completion script", async () => {
const quitSpy = spyOn(postmortem, "quit").mockResolvedValue(undefined);
const writeSpy = spyOn(Bun, "write").mockResolvedValue(0);
const config = { bin: "omp", version: "0.0.0", commands: new Map() };
const cmd = new Completions(["zsh"], config);
await cmd.run();
expect(writeSpy).toHaveBeenCalled();
expect(quitSpy).toHaveBeenCalledWith(0);
});
});