Summary
Every avl compile invocation fails on Windows with a generic IO_FAILED error at the publication step. The root cause is syncDirectory() in the compiler, which opens a directory with fs.open(path, "r") and calls handle.sync() — a POSIX-only durability pattern. On Windows, opening a directory handle this way is not supported, so publication always throws, the rollback path (which also calls syncDirectory()) fails too, and the real error gets swallowed into an AggregateError that the CLI never prints.
As far as I can tell, this makes the compiler unusable for all Windows users in v1.0.
Environment
- Windows 11 Pro (build 10.0.26200)
- Node.js v24.13.0
- FFmpeg / FFprobe 8.0.1 (gyan.dev essentials build), on PATH
- Repo cloned from
main on 2026-07-15
Steps to reproduce
git clone https://github.com/pixel-point/aval.git
cd aval
npm install
npm run build:public-packages
cd examples/grass-rabbit
node ../../packages/compiler/dist/cli.js compile motion.json --out public/rabbit-local.avl
Actual result
Fails immediately (before any FFmpeg work is visible):
ERROR IO_FAILED — ...\examples\grass-rabbit\public\rabbit-local.avl — Publication failed and the previous output pair could not be restored
Same result with --force, with a fresh output filename, and with absolute --ffmpeg/--ffprobe paths.
Root cause
packages/compiler/src/compile/output.ts:
export async function syncDirectory(path: string): Promise<void> {
const handle = await open(path, "r");
try {
await handle.sync();
} finally {
await handle.close();
}
}
Opening a directory with fs.open(dir, "r") throws on Windows (EISDIR/EPERM depending on the path), so every publication path that calls syncDirectory() fails: commands/compile-publication.ts, commands/dev.ts, commands/init-publication.ts. Because the rollback also calls it, rollbackFailures.length > 0 and the code lands on the generic "could not be restored" branch, hiding the underlying exception (it lives in cause: new AggregateError(...) but the CLI output does not surface it).
Suggested fix
Directory-handle fsync simply isn't available on Windows; a platform guard preserves the durability guarantee where it exists:
export async function syncDirectory(path: string): Promise<void> {
if (process.platform === "win32") return; // directory fsync unsupported on Windows
const handle = await open(path, "r");
try {
await handle.sync();
} finally {
await handle.close();
}
}
(Alternatively: try/catch ignoring EISDIR/EPERM/ENOTSUP.)
It would also help debugging a lot if the CLI printed the cause chain for IO_FAILED — the real error is currently invisible.
Verification of the fix
With the early-return patch applied locally to the built output, the grass-rabbit example compiles successfully on the same machine:
Reports: continuity 8 passed, alpha frames 311
- Asset: 1,186,928 bytes; SHA-256
77dd5de705e24d9e149c30ea6252afb9a4c97637cf25ebef239e31a78e691c9b (matches the committed example asset almost byte-for-byte — deterministic output confirmed)
- The example page runs and the hover state graph transitions correctly (
idle → entering → hover → exiting) in Chrome via WebCodecs/WebGL2.
Great project — the single-source-MP4 + frame-range authoring model is exactly what motion designers coming from After Effects need. Happy to test a proper fix on Windows.
Summary
Every
avl compileinvocation fails on Windows with a genericIO_FAILEDerror at the publication step. The root cause issyncDirectory()in the compiler, which opens a directory withfs.open(path, "r")and callshandle.sync()— a POSIX-only durability pattern. On Windows, opening a directory handle this way is not supported, so publication always throws, the rollback path (which also callssyncDirectory()) fails too, and the real error gets swallowed into anAggregateErrorthat the CLI never prints.As far as I can tell, this makes the compiler unusable for all Windows users in v1.0.
Environment
mainon 2026-07-15Steps to reproduce
Actual result
Fails immediately (before any FFmpeg work is visible):
Same result with
--force, with a fresh output filename, and with absolute--ffmpeg/--ffprobepaths.Root cause
packages/compiler/src/compile/output.ts:Opening a directory with
fs.open(dir, "r")throws on Windows (EISDIR/EPERM depending on the path), so every publication path that callssyncDirectory()fails:commands/compile-publication.ts,commands/dev.ts,commands/init-publication.ts. Because the rollback also calls it,rollbackFailures.length > 0and the code lands on the generic "could not be restored" branch, hiding the underlying exception (it lives incause: new AggregateError(...)but the CLI output does not surface it).Suggested fix
Directory-handle fsync simply isn't available on Windows; a platform guard preserves the durability guarantee where it exists:
(Alternatively: try/catch ignoring
EISDIR/EPERM/ENOTSUP.)It would also help debugging a lot if the CLI printed the
causechain forIO_FAILED— the real error is currently invisible.Verification of the fix
With the early-return patch applied locally to the built output, the grass-rabbit example compiles successfully on the same machine:
Reports: continuity 8 passed, alpha frames 31177dd5de705e24d9e149c30ea6252afb9a4c97637cf25ebef239e31a78e691c9b(matches the committed example asset almost byte-for-byte — deterministic output confirmed)idle → entering → hover → exiting) in Chrome via WebCodecs/WebGL2.Great project — the single-source-MP4 + frame-range authoring model is exactly what motion designers coming from After Effects need. Happy to test a proper fix on Windows.