From a5be596fcb8a573bd141d49616385f4ba0090325 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 01:56:46 +0000 Subject: [PATCH 1/2] Initial plan From 768f4355ff7bd2c2c87f84c50a47020c2e9d80e7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 02:01:40 +0000 Subject: [PATCH 2/2] fix: close daemon socket after load() to prevent process hang - Add socket.unref() in DaemonClient.connectToSocket so the idle socket does not keep the event loop alive after all messages have resolved. - Add cleanupDaemonClient() helper in local-encrypt/index.ts. - Call cleanupDaemonClient() at the end of the programmatic load() function so the daemon socket is explicitly closed after env resolution completes. - Export a public cleanup() function from varlock/src/index.ts for programmatic users who want explicit lifecycle control. --- packages/varlock/src/index.ts | 16 ++++++++++++++++ .../src/lib/local-encrypt/daemon-client.ts | 4 ++++ packages/varlock/src/lib/local-encrypt/index.ts | 5 +++++ 3 files changed, 25 insertions(+) diff --git a/packages/varlock/src/index.ts b/packages/varlock/src/index.ts index 27b7ae843..fcda3a7d3 100644 --- a/packages/varlock/src/index.ts +++ b/packages/varlock/src/index.ts @@ -2,6 +2,7 @@ import { checkForConfigErrors } from './cli/helpers/error-checks'; import { loadVarlockEnvGraph } from './lib/load-graph'; import { initVarlockEnv } from './runtime/env'; import { checkBunVersion } from './lib/check-bun-version'; +import { cleanupDaemonClient } from './lib/local-encrypt'; // Import env-graph components for internal API import { @@ -26,9 +27,24 @@ export async function load() { // loadFromSerializedGraph(envGraph.getSerializedGraph()); process.env.__VARLOCK_ENV = JSON.stringify(envGraph.getSerializedGraph()); initVarlockEnv(); + // Close daemon socket so the process can exit naturally after load() resolves. + // The socket is unref'd by default (see DaemonClient.connectToSocket), but + // explicitly closing it here is belt-and-suspenders for runtimes/environments + // where unref() may not be sufficient. + cleanupDaemonClient(); // TODO: return resolved env and schema / meta info } +/** + * Close the daemon client socket opened during `load()` or other operations + * that resolved `keychain(...)` values. This is called automatically by + * `load()`, but you can call it explicitly if you manage the connection + * lifecycle yourself. + */ +export function cleanup() { + cleanupDaemonClient(); +} + export function getBuildTimeReplacements(opts?: { objectKey?: string, diff --git a/packages/varlock/src/lib/local-encrypt/daemon-client.ts b/packages/varlock/src/lib/local-encrypt/daemon-client.ts index 78834d57e..e6131141a 100644 --- a/packages/varlock/src/lib/local-encrypt/daemon-client.ts +++ b/packages/varlock/src/lib/local-encrypt/daemon-client.ts @@ -441,6 +441,10 @@ export class DaemonClient { socket.on('connect', () => { clearTimeout(timeout); this.socket = socket; + // Unref the socket so it does not keep the process alive when idle. + // Pending sendMessage calls re-ref the socket via the timeout timer, + // so interactive/biometric operations are not affected. + this.socket.unref(); this.isConnected = true; this.buffer = Buffer.alloc(0); resolve(); diff --git a/packages/varlock/src/lib/local-encrypt/index.ts b/packages/varlock/src/lib/local-encrypt/index.ts index 5265af4ca..2e98dc2f1 100644 --- a/packages/varlock/src/lib/local-encrypt/index.ts +++ b/packages/varlock/src/lib/local-encrypt/index.ts @@ -286,6 +286,11 @@ export function getDaemonClient(): DaemonClient { return daemonClient; } +/** Close the daemon client socket if one was created. Safe to call multiple times. */ +export function cleanupDaemonClient(): void { + daemonClient?.cleanup(); +} + // ── Key management ───────────────────────────────────────────────────── /** Check if a key exists. */