Skip to content

safe-bash 0.1.423: Bun-hosted Miniflare recreation stalls after output-budget abort #662

Description

@kamilio

A safe-bash/safe-fs upgrade from 0.1.70 to 0.1.423 exposes a Worker recreation failure in our Bun-hosted Miniflare tests after a shell output-budget abort. The same new-version lifecycle runner completes under Node. This appears to involve Bun/Miniflare subprocess or stream cleanup; we have not established that safe-bash command semantics or production Workers are broken.

This is separate from #649's portable export/provider API gaps.

Controlled comparison

We copied the original application shell sources into an isolated temporary package and kept the same Worker fixture, assertions, Wrangler bundling, compatibility flags, and backend harness. Only the temporary safe-bash and safe-fs dependency versions changed between these two runs:

Original shell sources Dependencies Paired Worker tests
Unchanged safe-bash + safe-fs 0.1.70 11 passed, 45 assertions, 7.01s
Unchanged safe-bash + safe-fs 0.1.423 First 8 passed; next Worker readiness timed out, 17.59s

The first test file covers tee, append, split UTF-8, binary output, and two output-budget failures. After disposing that Miniflare instance, the next file creates a new Worker for redirection tests. With 0.1.423 it stalls at worker.ready. Omitting the two budget-abort cases lets the paired tests pass. A same-file dispose/recreate experiment also reproduced the readiness timeout, so the file boundary itself is not required.

The failing command is:

curl https://download.example.com/large | tee /keep.txt | wc -c

The host returns a 1 MiB binary response. The shell's output budget is 64 KiB, and the request correctly rejects with Shell limit exceeded: maxOutputBytes. Failure occurs later when recreating a Worker, rather than in that assertion.

Bun versus Node lifecycle isolation

Using the same actual Wrangler-bundled shell fixture and a minimal Miniflare host loop:

  • Bun: iterations 0–5 complete request, expected budget rejection, and disposal; iteration 6 times out awaiting readiness.
  • Node: all 10 iterations complete successfully.
  • An earlier three-iteration Bun run surfaced node:child_process #createStdioObject connect ENOENT; another three-iteration run passed. The failure is timing-sensitive, not guaranteed on every short run.

Observed diagnostic:

workerd/server/server.c++:6811: error: ... Broken pipe; fd = 3
...
Worker readiness timed out

The reduced host runner below was verified against our application shell fixture. It is not yet a standalone safe-bash-only reproduction: shell-worker.js is the existing Wrangler bundle, whose POST endpoint constructs a fresh shell, executes {script}, returns the result, and converts thrown limits to {error}. Its curl transport uses Worker fetch; the dummy TILES binding is not reached by this rejected pipeline, so native application storage is not needed for this reduced path.

import { readFileSync } from 'node:fs';
import { Miniflare, Response } from 'miniflare';

const script = readFileSync('./shell-worker.js', 'utf8');
for (let i = 0; i < 10; i++) {
  const worker = new Miniflare({
    modules: true,
    script,
    compatibilityDate: '2025-01-01',
    compatibilityFlags: [
      'nodejs_compat_v2',
      'global_fetch_strictly_public',
      'enable_nodejs_http_modules',
    ],
    cf: false,
    bindings: {
      EXECUTE_SHELL_NETWORK_ENABLED: 'true',
      AGENT_ENTITY_INTERNAL_TOKEN: 'test',
      TEST_AGENT_ID: 'agent_test',
    },
    serviceBindings: { TILES: () => new Response('ok') },
    outboundService: {
      node(request, response) {
        const bytes = new Uint8Array(1024 * 1024).fill(255);
        response.writeHead(200, {'content-type': 'application/octet-stream'});
        response.write(bytes, () => response.end(bytes.subarray(bytes.length)));
      },
    },
  });
  const deadline = setTimeout(() => {
    console.error('timeout', i);
    process.exit(1);
  }, 5000);
  console.log('ready', i, String(await worker.ready));
  const result = await worker.dispatchFetch('https://shell.test/', {
    method: 'POST',
    body: JSON.stringify({
      script: 'curl https://download.example.com/large | tee /keep.txt | wc -c',
    }),
  });
  console.log(await result.text());
  await worker.dispose();
  clearTimeout(deadline);
  console.log('disposed', i);
}

Run the same .mjs file with bun and node. The normal paired suite uses a 10-second readiness deadline; the reduced runner uses 5 seconds.

Environment

  • macOS arm64
  • Bun reports 1.3.11; test banner: 1.3.11-canary.1 (687700d8)
  • Node 22.23.2
  • Miniflare 4.20260708.1
  • workerd 1.20260708.1
  • Wrangler 4.110.0, actual deploy --dry-run bundle
  • Worker compatibility date and flags as shown above

Requested guidance

Please help identify whether changed abort/stream cleanup in safe-bash or safe-fs triggers a Bun/Miniflare lifecycle defect, and establish the supported Worker/Bun test path. A minimized upstream regression test should cover output-limit rejection followed by disposal and repeated Worker creation. We are not requesting weaker output limits, skipped failure cases, or treating the successful Node run as proof that the Bun integration is healthy.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions