Lying to Zed to get its remote server running on FreeBSD A developer documented running Zed's remote server on FreeBSD by installing the linux_base-rl9 compatibility layer and placing a wrapper script at ~/.local/bin/uname that reports "Linux x86_64" only for the `-sm`/`-ms` queries, so Zed uploads its Linux server to ~/.zed_server/ instead of rejecting the host. The setup also requires FreeBSD commit ec6629628cb, present in main, stable/15 and releng/15.2, which fixes a directory-listing bug that otherwise leaves the project tree empty with "Invalid argument (os error 22)"; without the fix, a quiet variant of the bug dropped 112 of 1,002 files from the listing. The author, who disables Zed's AI features via `"disable_ai": true` and uses OpenCode for regression testing and edge-case work, called the uname wrapper "a small lie, but a contained one" and said it is the part he would most like to retire. I’ve used VS Code since I was about 16, so for almost ten years and honestly it was mostly fine. But as of late it has become unbearable: slow, and increasingly weighed down by AI slop, on the other hand Zed is fast, and it has a native switch that turns all of its AI stuff off, which I absolutely adore. It’s a toggle in Settings, or "disable ai": true in your settings file. That’s not because I avoid LLMs. I just don’t want them trying to edit every line I write, or sitting in the corner judging me ominously. I still write most of my FOSS code myself: it keeps me sane, and I feel FOSS developers have a responsibility to keep code clean for LLMs to steal from lol. What I do hand off is the tiring part: regression testing, edge cases, and exploration beyond what my tiny 20-watt brain can hold. For that I use harnesses that work well with models, like OpenCode https://opencode.ai . Beyond auto-complete.. I’ve always found entire harnesses like claude code etc. very clunky in editors. Getting it working Zed has no FreeBSD remote server offering yet , but FreeBSD can run Linux binaries in spades, so we use Zed’s Linux one. All you need is a FreeBSD server you can SSH into. 1. Turn on Linux compatibility sudo sysrc linux enable=YES sudo service linux start sudo pkg install linux base-rl9 kldstat -m linux64 should print a module 2. Make Zed pick the Linux server Zed asks the host what platform it runs. FreeBSD says FreeBSD amd64 , there’s no server for that, and Zed gives up. This wrapper says Linux x86 64 to that one question and passes everything else to the real uname . Save it as ~/.local/bin/uname and make it executable: bash /bin/sh case "$ :$ " in 1:-sm|1:-ms printf 'Linux x86 64\n' ;; exec /usr/bin/uname "$@" ;; esac Put ~/.local/bin on PATH for non-interactive SSH sessions. For zsh, add this to ~/.zshenv use your shell’s equivalent : export PATH="$HOME/.local/bin:$PATH" It’s a small lie, but a contained one, and the part I’d love to retire. 3. Connect. Connect over SSH from Zed and open a folder. The first connection uploads the Linux server to ~/.zed server/ . To watch, run tail -f ~/.local/share/zed/logs/server-workspace- .log . 4. Run a kernel with the FreeBSD fix Without it you’ll connect fine and get an empty project tree, with this in the server log: error processing entry failed to read directory entry Caused by: Invalid argument os error 22 The fix is ec6629628cb https://github.com/freebsd/freebsd-src/commit/ec6629628cbed2816f27aed8a6d5f9eb3521c1c1 , in main , stable/15 and releng/15.2 , so 15.2-RELEASE will include it. If the error persists, check you booted the new kernel with uname -v . The bug in layman terms To show your project, Zed asks the server for the names of the files in each folder, a few at a time. Zed sets aside a small amount of space for each batch. Picture a teacher reading a class roll to a student who writes the names on a sticky note I couldn’t come up with a better analogy, bare with me . The teacher crosses off each name as she reads it, and she never reads a crossed-off name again. The student’s note has room for 6 names. The teacher reads in groups of 4, a sensible habit that works fine almost every time. But 6 isn’t a whole number of groups, so she reads 8 names and crosses all 8 off. Only 6 fit on the note. Now she’s in a bind. Her slip was grabbing more than fit before checking. She can’t hand over the 6 that fit and keep the other 2 for later, because they’re already crossed off and she would never read them again. So she reports an error instead, which at least is loud. When I tried the quiet way, 112 of my 1,002 files vanished from the listing with no warning. A loud error beats a quiet loss I’m pretty sure. Zed saw the error and decided it couldn’t read the folder, and showed it up empty. The fix is small: read no more names than fit on the note. Read 6, cross off 6, come back for the next 6, until the roll is done. In the real bug, the teacher is FreeBSD’s Linux translator Linuxulator , the note is a 768-byte buffer, and a group of 4 names is a 512-byte block. For the kernel nerds This is a regression. A February 2026 fix for NFS directories returning bogus entries 110f2567cb51 https://github.com/freebsd/freebsd-src/commit/110f2567cb51f1eeddbd5d9937000ad64f6dc746 , PR 292282 https://bugs.freebsd.org/bugzilla/show bug.cgi?id=292282 changed how sys/compat/linux/linux file.c sizes its native read, in both linux getdents and linux getdents64 : php / before February 2026 / buflen = min args- count, MAXBSIZE ; / after: every request rounded up to a 512-byte block / buflen = min roundup2 args- count, DEV BSIZE , MAXBSIZE ; If the caller’s buffer isn’t a multiple of 512 bytes, the native batch can outgrow it. The emulation layer converts records until one doesn’t fit and returns EINVAL , but kern getdirentries has already advanced the directory offset past the whole batch. That’s why returning a partial batch isn’t enough on its own. The fix ec6629628cb https://github.com/freebsd/freebsd-src/commit/ec6629628cbed2816f27aed8a6d5f9eb3521c1c1 , reviewed in D60760 https://reviews.freebsd.org/D60760 : php if args- count = DEV BSIZE buflen = min args- count, MAXBSIZE ; else buflen = roundup2 args- count, DEV BSIZE ; It’s safe because Linux directory records are never larger than FreeBSD’s: for dirent64 the header is 19 bytes against 24, and both round each record up to a multiple of 8. A native batch that fits in the caller’s buffer translates into one that fits too. Legacy getdents had the same bug for 32-bit and 64-bit callers. My first patch only fixed getdents64 ; fixing both took the test matrix from 86 failures to zero. Acknowledgements I’ve had plenty of patches merged before this one, but this was among the quickest to land in releng/15.2 : it didn’t sit in stable for long thankfully . The release engineering team re@ runs a really cool process that I am yet to fully understand in depth. Thank you also to Kyle Evans kevans@ and Mark Johnston markj@ for reviewing the patch and for their notes along the way.