Summary
Building packages.aarch64-linux.sample_environments_farm (and the individual mega / julia110 / julia111 environments) on an x86_64-linux host via QEMU user-mode (binfmt) emulation fails in the Julia kernel build. The Julia julia-depot / julia-symbols derivations die during package precompilation with:
nested task error: IOError: TTY: invalid argument (EINVAL)
[2] Base.TTY(fd::RawFD) @ Base ./stream.jl:251
[3] open_fake_pty() @ REPL.Precompile.FakePTYs .../REPL/src/precompile.jl ...
The root cause is not in codedown-languages or Julia. It is a QEMU user-mode emulation bug: with glibc ≥ 2.42, isatty() uses the TCGETS2 ioctl, which QEMU does not translate, so isatty() on a PTY master returns 0 under emulation. This is enough to break libuv's uv_tty_init, which Julia's REPL precompile workload relies on.
We are filing this to track the situation and pick up an eventual QEMU fix, rather than carrying a workaround.
Environment
- Build host:
x86_64-linux, with aarch64-linux enabled via Nix extra-platforms + QEMU binfmt (/run/binfmt/aarch64-linux → qemu-aarch64-binfmt-P).
- QEMU: 10.1.2
- nixpkgs:
release-25.11 (flake input; rev f5190b6…), which ships glibc 2.42
- Julia: 1.11.9 (source build; interpreter linked against
glibc-2.42-61)
- Affected derivations:
julia-depot → julia-symbols → julia → julia110 / julia111 / mega → sample_environments_farm
Root cause chain
- nixpkgs 25.11 ships glibc 2.42.
- glibc 2.42 reimplemented
isatty() to issue ioctl(fd, TCGETS2) instead of TCGETS.
- QEMU user-mode's ioctl translation table (
linux-user/ioctls.h) handles TCGETS but not TCGETS2, so the ioctl is silently dropped and isatty() fails.
- Julia's REPL precompile workload (
stdlib/REPL/src/precompile.jl, guarded by if Base.generating_output()) calls open_fake_pty() (share/julia/test/testhelpers/FakePTYs.jl), which builds a PTY master and wraps it with Base.TTY(RawFD(fdm)) → libuv uv_tty_init.
- Because
isatty(master) is 0 under QEMU, libuv classifies the fd as a non-tty and returns EINVAL. The REPL precompile dies, cascading to every package that depends on REPL (IJulia, Pkg, LanguageServer, SymbolServer, Plots, …).
Reproduction (isolates it to QEMU + isatty)
Run a minimal version of open_fake_pty() with the project's aarch64 Julia (auto-runs under QEMU via binfmt):
# /tmp/pty_repro.jl
O_RDWR = Base.Filesystem.JL_O_RDWR; O_NOCTTY = Base.Filesystem.JL_O_NOCTTY
fdm = ccall(:posix_openpt, Cint, (Cint,), O_RDWR | O_NOCTTY)
println("posix_openpt -> ", fdm)
println("grantpt -> ", ccall(:grantpt, Cint, (Cint,), fdm))
println("unlockpt -> ", ccall(:unlockpt, Cint, (Cint,), fdm))
println("isatty(master) -> ", ccall(:isatty, Cint, (Cint,), fdm))
try; Base.TTY(RawFD(fdm)); println("Base.TTY OK")
catch e; println("Base.TTY FAILED: ", sprint(showerror, e)); end
Output under QEMU (aarch64):
posix_openpt -> 17
grantpt -> 0
unlockpt -> 0
isatty(master) -> 0 # <-- wrong; should be 1
Base.TTY FAILED: IOError: TTY: invalid argument (EINVAL)
strace -f -e trace=ioctl on the same run shows only TIOCGPTN and TIOCSPTLCK forwarded to the host for the master fd — the TCGETS2 from isatty() never appears (QEMU drops it).
Native control (compiled C on the x86_64 host, no QEMU): isatty(master) = 1.
Upstream status
- Tracked as Ubuntu LP #2133804 ("QEMU does not emulate IOCTL TCGETS2") and LP #2133188.
- A proper fix requires
host_to_target_termios2() / target_to_host_termios2() in linux-user/syscall.c.
- Not fixed in any released QEMU, nor in QEMU
master as of this writing — linux-user/syscall.c only has a PowerPC #define TCGETS2 TCGETS alias, no general termios2 translation.
Summary
Building
packages.aarch64-linux.sample_environments_farm(and the individualmega/julia110/julia111environments) on an x86_64-linux host via QEMU user-mode (binfmt) emulation fails in the Julia kernel build. The Juliajulia-depot/julia-symbolsderivations die during package precompilation with:The root cause is not in codedown-languages or Julia. It is a QEMU user-mode emulation bug: with glibc ≥ 2.42,
isatty()uses theTCGETS2ioctl, which QEMU does not translate, soisatty()on a PTY master returns0under emulation. This is enough to break libuv'suv_tty_init, which Julia's REPL precompile workload relies on.We are filing this to track the situation and pick up an eventual QEMU fix, rather than carrying a workaround.
Environment
x86_64-linux, withaarch64-linuxenabled via Nixextra-platforms+ QEMU binfmt (/run/binfmt/aarch64-linux→qemu-aarch64-binfmt-P).release-25.11(flake input; revf5190b6…), which ships glibc 2.42glibc-2.42-61)julia-depot→julia-symbols→julia→julia110/julia111/mega→sample_environments_farmRoot cause chain
isatty()to issueioctl(fd, TCGETS2)instead ofTCGETS.linux-user/ioctls.h) handlesTCGETSbut notTCGETS2, so the ioctl is silently dropped andisatty()fails.stdlib/REPL/src/precompile.jl, guarded byif Base.generating_output()) callsopen_fake_pty()(share/julia/test/testhelpers/FakePTYs.jl), which builds a PTY master and wraps it withBase.TTY(RawFD(fdm))→ libuvuv_tty_init.isatty(master)is0under QEMU, libuv classifies the fd as a non-tty and returnsEINVAL. The REPL precompile dies, cascading to every package that depends on REPL (IJulia, Pkg, LanguageServer, SymbolServer, Plots, …).Reproduction (isolates it to QEMU +
isatty)Run a minimal version of
open_fake_pty()with the project's aarch64 Julia (auto-runs under QEMU via binfmt):Output under QEMU (aarch64):
strace -f -e trace=ioctlon the same run shows onlyTIOCGPTNandTIOCSPTLCKforwarded to the host for the master fd — theTCGETS2fromisatty()never appears (QEMU drops it).Native control (compiled C on the x86_64 host, no QEMU):
isatty(master) = 1.Upstream status
host_to_target_termios2()/target_to_host_termios2()inlinux-user/syscall.c.masteras of this writing —linux-user/syscall.conly has a PowerPC#define TCGETS2 TCGETSalias, no generaltermios2translation.