diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 4901d84f7..0680ca84f 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed `omp completions ` 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 diff --git a/packages/coding-agent/src/commands/completions.ts b/packages/coding-agent/src/commands/completions.ts index e7182b7ae..8c1d8202e 100644 --- a/packages/coding-agent/src/commands/completions.ts +++ b/packages/coding-agent/src/commands/completions.ts @@ -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); } } diff --git a/packages/coding-agent/test/cli-completions-exit.test.ts b/packages/coding-agent/test/cli-completions-exit.test.ts new file mode 100644 index 000000000..5f631f60e --- /dev/null +++ b/packages/coding-agent/test/cli-completions-exit.test.ts @@ -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); + }); +});