Skip to content

[BUG]: Programmatic load() can leave daemon socket open after resolving keychain(...) #716

@orelbn

Description

@orelbn

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().

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions