What happened
@flue/runtime@2.0.3's Cloudflare sandbox adapter, cfSandboxToSandbox().readdir in dist/cloudflare/index.mjs, lists a directory with:
async readdir(path) {
const result = await guarded("readdir", sandbox.exec(`find ${shellQuote(path)} -mindepth 1 -maxdepth 1 -printf '%f\0'`));
if (!result.success) throw new Error(`readdir failed for ${path}: ${result.stderr}`);
return result.stdout.split("\0").filter((s) => s.length > 0);
}
The Cloudflare @cloudflare/sandbox Durable Object's exec strips NUL bytes from returned stdout, so a directory listing with more than one entry comes back as a single string with every filename concatenated together — there is nothing left in stdout to split on "\0".
Evidence (production, 2026-09-02, task 01M1FQ8DZ1SRDXWV6SPRA9R41M)
sandbox.exec success find '/workspace/.agents/skills' -mindepth 1 -maxdepth 1 -printf '%f\0'
sandbox.exec success stat -L -c '%s/%Y/%F' '/workspace/.agents/skills/delivery-reportconventional-commits' && ...
sandbox.exec success find '/workspace' -mindepth 1 -maxdepth 1 -printf '%f\0'
sandbox.exec success ls /workspace; ... cat /workspace/AGENTS.md.agents 2>/dev/null | head -50
/workspace/.agents/skills actually contains two directories, delivery-report and conventional-commits (see delivery/conventions.ts in our repo, which writes both). Flue's skill discovery saw one concatenated name delivery-reportconventional-commits, the follow-up stat on that name failed, and neither skill was ever loaded. Separately, the model itself ended up reading a concatenated top-level listing and guessing at a nonexistent file AGENTS.md.agents (really two entries, AGENTS.md and .agents, joined together).
Impact
Every agent using cloudflareSandbox(...) is affected, and specifically: skill auto-discovery over the Cloudflare sandbox adapter is silently broken whenever a directory being listed has more than one entry whose concatenation doesn't happen to stat successfully as one path (and even when it does, the wrong path is read).
Suggested fix
Use a delimiter that survives the sandbox's exec transport — a newline works, since find -printf '%f\n' output split on "\n" (filtering empty lines) doesn't rely on NUL bytes surviving the round trip. A filename containing a literal newline is an edge case most callers can accept losing to the same degree the existing NUL-based approach already assumed it wouldn't occur in practice.
Workaround
We've shipped a local wrapper around cloudflareSandbox(...) that overrides readdir with the newline-based version until a fixed @flue/runtime is available and pinned. See rp4rk/factori#364.
What happened
@flue/runtime@2.0.3's Cloudflare sandbox adapter,cfSandboxToSandbox().readdirindist/cloudflare/index.mjs, lists a directory with:The Cloudflare
@cloudflare/sandboxDurable Object'sexecstrips NUL bytes from returned stdout, so a directory listing with more than one entry comes back as a single string with every filename concatenated together — there is nothing left instdoutto split on"\0".Evidence (production, 2026-09-02, task
01M1FQ8DZ1SRDXWV6SPRA9R41M)/workspace/.agents/skillsactually contains two directories,delivery-reportandconventional-commits(seedelivery/conventions.tsin our repo, which writes both). Flue's skill discovery saw one concatenated namedelivery-reportconventional-commits, the follow-upstaton that name failed, and neither skill was ever loaded. Separately, the model itself ended up reading a concatenated top-level listing and guessing at a nonexistent fileAGENTS.md.agents(really two entries,AGENTS.mdand.agents, joined together).Impact
Every agent using
cloudflareSandbox(...)is affected, and specifically: skill auto-discovery over the Cloudflare sandbox adapter is silently broken whenever a directory being listed has more than one entry whose concatenation doesn't happen tostatsuccessfully as one path (and even when it does, the wrong path is read).Suggested fix
Use a delimiter that survives the sandbox's
exectransport — a newline works, sincefind -printf '%f\n'output split on"\n"(filtering empty lines) doesn't rely on NUL bytes surviving the round trip. A filename containing a literal newline is an edge case most callers can accept losing to the same degree the existing NUL-based approach already assumed it wouldn't occur in practice.Workaround
We've shipped a local wrapper around
cloudflareSandbox(...)that overridesreaddirwith the newline-based version until a fixed@flue/runtimeis available and pinned. See rp4rk/factori#364.