You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Bun.write(Bun.stdout, data) pads an appended file with NUL bytes when data is over 1024 bytes (fallocate on an O_APPEND fd) #44387
It needs stdout opened with O_APPEND (>>), more than 1024 bytes written, and a file shorter than the data. With 1024 bytes, or with a file already longer than the data, the output is correct.
What is the expected behavior?
out.log is abc\n followed by the 2000 bytes, 2004 bytes in total, as with process.stdout.write or fs.writeFileSync(1, …).
What do you see instead?
4000 bytes: abc\n, then 1996 NUL bytes, then the data.
fallocate with mode 0 grows the file to 2000 bytes, and the O_APPEND write then lands after the new end.
Additional information
The preallocation is in WriteFile: for more than 1024 bytes on Linux it calls preallocate_file(fd, 0, len), which is fallocate(fd, 0, 0, len), without checking how the fd was opened. The equivalent preallocation in node:fsalready skips preallocation for O_APPEND fds and uses the current offset from lseek; doing the same here, or passing FALLOC_FL_KEEP_SIZE, would fix it.
We hit it in a merge script: the JSON output of a Bun script, appended to a log with >>, was preceded by 211,016 NUL bytes.
Open PR #41685 fixes this: it skips the preallocation when the fd has O_APPEND, the same check node:fs makes. It also fixes a second failure on the same kind of fd, which we hit on 1.4.2 as well: Bun.write(Bun.stdout, Bun.file(p)) under >> rejects with EBADF: bad file descriptor, copy_file_range.
#41685 no longer skips the preallocation. It now has only the EBADF: bad file descriptor, copy_file_range change for Bun.write(fd, Bun.file(p)) on an O_APPEND fd, and it stops clearing O_APPEND on a shared tty. The NUL padding that this issue reports is handled in #44425, which makes the preallocation keep the file size for every caller.
I ran the repro above on a debug build of main with both PRs applied: out.log is 2004 bytes with no NUL byte, and Bun.write(Bun.stdout, Bun.file(p)) under >> appends and exits 0. On a build of main without them it is 4000 bytes with 1996 NUL bytes, and the file source rejects with EBADF.
Activity
Vantrongs commentedon Oct 1, 2026
Open PR #41685 fixes this: it skips the preallocation when the fd has
O_APPEND, the same checknode:fsmakes. It also fixes a second failure on the same kind of fd, which we hit on 1.4.2 as well:Bun.write(Bun.stdout, Bun.file(p))under>>rejects withEBADF: bad file descriptor, copy_file_range.robobun commentedon Oct 3, 2026
An update on the two PRs named in this issue.
#41685 no longer skips the preallocation. It now has only the
EBADF: bad file descriptor, copy_file_rangechange forBun.write(fd, Bun.file(p))on an O_APPEND fd, and it stops clearing O_APPEND on a shared tty. The NUL padding that this issue reports is handled in #44425, which makes the preallocation keep the file size for every caller.I ran the repro above on a debug build of main with both PRs applied:
out.logis 2004 bytes with no NUL byte, andBun.write(Bun.stdout, Bun.file(p))under>>appends and exits 0. On a build of main without them it is 4000 bytes with 1996 NUL bytes, and the file source rejects with EBADF.