Skip to content

Fix varlock run dropping piped output on exit - #1296

Merged
theoephraim merged 3 commits into
mainfrom
fix-run-piped-output-drain
Nov 12, 2026
Merged

theoephraim merged 3 commits into
mainfrom
fix-run-piped-output-drain

Conversation

@theoephraim

@theoephraim theoephraim commented Nov 12, 2026 •

Copy link
Copy Markdown
Member

Fixes #1279. Supersedes #1292.

When stdout/stderr are piped (redaction on), varlock run and varlock proxy run could lose output:

  • exec settled on the child's exit event, which can fire before its pipes are read. Output still unread, or written by a background process the child started, was dropped. It now waits for the pipes to close, capped at 2s after exit so a backgrounded process holding the pipe (sh -c 'server &') can't hang varlock. After the cap the pipes are destroyed (previous behavior, just later).
  • gracefulExit only flushes stdio for 1s, so a slow reader (varlock run -- cmd | (sleep 2; wc -c)) got 64KB of a 2MB write. We now flush our own stdout/stderr first, blocking on the reader like a direct pipe would. Bun's stdio bypasses the Writable queue (writableLength stays 0), so the flush tracks write() returning false and waits for drain, which works in both runtimes.
  • Since that flush has no time limit, signals after the child exits are no longer swallowed by the forwarder. A signal during post-child cleanup (proxy audit log flush, session record) is held until cleanup finishes; beginFinalFlush() then exits with 128+N, and any signal during the flush exits immediately.

No added delay in the normal case: the wait ends when the pipes close, which is at child exit unless a descendant holds them. TTY/inherited stdio has no pipes and no wait.

Wait for the child's piped stdout/stderr to close after it exits (bounded to 2s
so a backgrounded process holding the pipe can't hang varlock), and flush our
own stdout/stderr before gracefulExit, whose 1s flush cap truncated output to
slow readers.

Fixes #1279
@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

bumpy-frog

The changes in this PR will be included in the next version bump.

patch Patch releases

  • varlock 1.24.0 → 1.24.1

Bump files in this PR

Click here if you want to add another bump file to this PR


This comment is maintained by bumpy.

@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

📦 Bundle size

⚠️ grows the bundle by 10.0 KB (+0.2%)

Metric main This PR Δ
Total dist 5204.5 KB 5214.5 KB +10.0 KB (+0.2%)
JS 1985.6 KB 1988.8 KB +3.2 KB (+0.2%)
Sourcemaps 3075.9 KB 3082.7 KB +6.8 KB (+0.2%)
Type defs 143.1 KB 143.1 KB —
Other 0.0 KB 0.0 KB —

dist/ only; native binaries are versioned separately and not counted here.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The slow-reader fix still drops output under Bun, and the new unlimited drain can prevent signal-driven shutdown after the child exits.

Reviewed changes: Reviewed the child-pipe lifecycle, parent stdio drain, both CLI integrations, regression tests, and documentation.

  • Child output lifetime: Delays exec settlement until piped streams close, with a two-second post-exit cap for descendants.
  • Parent output drain: Adds a pre-exit stdout/stderr flush to run and the shared proxy-run completion path.
  • Tests and documentation: Covers late descendant output and timeout behavior, and documents the bounded descendant wait.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

@pkg-pr-new

pkg-pr-new Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

varlock

npm i https://pkg.pr.new/varlock@1296

@varlock/astro-integration

npm i https://pkg.pr.new/@varlock/astro-integration@1296

@varlock/cloudflare-integration

npm i https://pkg.pr.new/@varlock/cloudflare-integration@1296

@varlock/nextjs-integration

npm i https://pkg.pr.new/@varlock/nextjs-integration@1296

@varlock/nuxt-integration

npm i https://pkg.pr.new/@varlock/nuxt-integration@1296

@varlock/vite-integration

npm i https://pkg.pr.new/@varlock/vite-integration@1296

@varlock/1password-plugin

npm i https://pkg.pr.new/@varlock/1password-plugin@1296

@varlock/aws-secrets-plugin

npm i https://pkg.pr.new/@varlock/aws-secrets-plugin@1296

@varlock/bitwarden-plugin

npm i https://pkg.pr.new/@varlock/bitwarden-plugin@1296

@varlock/keepass-plugin

npm i https://pkg.pr.new/@varlock/keepass-plugin@1296

@varlock/pass-plugin

npm i https://pkg.pr.new/@varlock/pass-plugin@1296

@varlock/proton-pass-plugin

npm i https://pkg.pr.new/@varlock/proton-pass-plugin@1296

commit: d629661

Bun's stdout/stderr bypass the Writable queue, so writableLength stays 0 and an
empty write's callback fires with output still queued. Track write() returning
false and wait for drain instead, which works in both runtimes.

After the child exits, a forwarded signal was a no-op, so a stalled reader made
varlock unkillable during the unbounded flush. Exit with 128+N instead.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes: Reviewed the output-drain and signal-lifecycle fixes added since the prior review.

  • Tracked backpressure: Added destination write()/drain tracking so Bun's native stdio buffer is drained before exit.
  • Restored termination: Made signals received after child detachment exit varlock with 128+N across the shared run paths.
  • Added regression coverage: Covered delayed readers under Node and Bun, late descendant output, and SIGTERM during a stalled drain. All four smoke tests and 12 focused unit tests passed.

Pullfrog  | View workflow run | Using gpt-6.1-sol | 𝕏

… flush

A signal after the child exits now waits for the caller's cleanup (proxy audit
log flush, session record) instead of cutting it short. beginFinalFlush() then
exits with 128+N if one arrived, and makes later signals exit immediately so a
stalled reader still can't make varlock unkillable.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes: Reviewed the signal-lifecycle changes since 659348b. The build, 13 focused unit tests, and all four output-drain smoke tests passed.

  • Held post-child signals until cleanup finishes, preserving the first signal's exit status.
  • Added final-flush transitions to run and both shared proxy-run paths so signals still terminate a stalled output drain.
  • Expanded signal tests to cover deferred cleanup termination and immediate final-flush termination.

Pullfrog  | View workflow run | Using gpt-6.1-sol | 𝕏

@theoephraim
theoephraim merged commit 9d9190b into main Nov 12, 2026
26 checks passed
theoephraim added a commit that referenced this pull request Nov 13, 2026
* Fix release PR smoke and framework test failures

Keep the literal '_VARLOCK_ENV_KEY is not set' in the runtime missing-key
error (changed in #1295), which framework tests assert on (and guard against
with shouldNotContain). Skip the two run-output-drain smoke tests on Windows,
where they have failed on every run since #1296.

* Add empty bump file
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG]: varlock run can drop piped output from the command it runs

1 participant