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.
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:
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 atworker.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:
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:
node:child_process #createStdioObject connect ENOENT; another three-iteration run passed. The failure is timing-sensitive, not guaranteed on every short run.Observed diagnostic:
The reduced host runner below was verified against our application shell fixture. It is not yet a standalone safe-bash-only reproduction:
shell-worker.jsis the existing Wrangler bundle, whose POST endpoint constructs a fresh shell, executes{script}, returns the result, and converts thrown limits to{error}. Itscurltransport uses Workerfetch; the dummy TILES binding is not reached by this rejected pipeline, so native application storage is not needed for this reduced path.Run the same
.mjsfile withbunandnode. The normal paired suite uses a 10-second readiness deadline; the reduced runner uses 5 seconds.Environment
1.3.11-canary.1 (687700d8)deploy --dry-runbundleRequested 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.