varlock version
varlock 1.2.0
Steps to reproduce
Note that the reproduction and issue were created using AI and verified by me. If there are any issues with the information below please let me know.
Minimal reproduction repository:
https://github.com/orelbn/varlock-keychain-hang-repro
Steps:
bun install
bun run setup:keychain
bun run repro
On first run, approve any macOS Keychain or biometric prompt for Varlock's native helper, then run bun run repro again if needed.
The repro is a Bun host script importing a library function that calls Varlock programmatically:
import { load } from "varlock";
export async function getMessage() {
await load();
return { ok: true };
}
The schema contains:
VARLOCK_REPRO_SECRET=keychain(service="dev.varlock.repro", account="example")
What is expected?
After await load() resolves environment values inside the imported function and the host script prints its output, the process exits normally.
What is actually happening?
The host script can print its output successfully but remain alive.
Inspecting the process with lsof shows an open Unix socket connected to Varlock's local encryption daemon socket:
~/.varlock/local-encrypt/daemon.sock
The daemon process itself is expected to remain running. The issue is that the host process appears to retain a live socket handle after await load() has finished resolving env values.
System Info
macOS
Bun
varlock 1.2.0
Repro uses Varlock through the package import, not through the `varlock` CLI binary.
Any additional comments?
Suspected cause: keychain(...) resolves through Varlock's daemon client singleton. The daemon client has a cleanup method that closes the socket, but the public programmatic load() API does not appear to close or unref that socket after env resolution, and there does not appear to be a documented cleanup API for short-lived scripts.
Possible fixes:
- Unref the daemon socket so it does not keep the process alive.
- Close the daemon client socket after
load() completes resolving env values.
- Expose a public cleanup/dispose API for programmatic users.
- Return a disposable/session object from
load().
varlock version
varlock 1.2.0
Steps to reproduce
Note that the reproduction and issue were created using AI and verified by me. If there are any issues with the information below please let me know.
Minimal reproduction repository:
https://github.com/orelbn/varlock-keychain-hang-repro
Steps:
On first run, approve any macOS Keychain or biometric prompt for Varlock's native helper, then run
bun run reproagain if needed.The repro is a Bun host script importing a library function that calls Varlock programmatically:
The schema contains:
What is expected?
After
await load()resolves environment values inside the imported function and the host script prints its output, the process exits normally.What is actually happening?
The host script can print its output successfully but remain alive.
Inspecting the process with
lsofshows an open Unix socket connected to Varlock's local encryption daemon socket:The daemon process itself is expected to remain running. The issue is that the host process appears to retain a live socket handle after
await load()has finished resolving env values.System Info
macOS Bun varlock 1.2.0 Repro uses Varlock through the package import, not through the `varlock` CLI binary.Any additional comments?
Suspected cause:
keychain(...)resolves through Varlock's daemon client singleton. The daemon client has a cleanup method that closes the socket, but the public programmaticload()API does not appear to close or unref that socket after env resolution, and there does not appear to be a documented cleanup API for short-lived scripts.Possible fixes:
load()completes resolving env values.load().