Repository navigation
Upgrade to Zig 0.17 - #1163
Merged
Merged
Upgrade to Zig 0.17#1163
Conversation
Build fx with Zig 0.17.0. build.zig.zon requires it, every workflow installs it, and PGSO qualification uses LLVM 22.1.8 to match. Zig 0.17 removes `**`, `errdefer |err|`, `@cImport`, and the old type info field lists. The source moves to `@splat`, a comptime `text_utils.repeat` helper, split cleanup functions, and the new field_names and field_types arrays. The Node-API addon translates node_api.h with a build step. Test fakes checked with `@hasDecl` become pub, because `@hasDecl` no longer sees private declarations. `zig build test` gives the test binary its fx and source paths through a generated test_paths module, since the build no longer exposes install paths for environment variables. Zig 0.17's SafeAllocator grows a small allocation in place only when it sits at the end of a bucket, and buckets are never reused, so an allocation-failure sweep could count a different number of allocations on each run. Sweeps and allocation-count tests now use testing_allocator.no_resize, which never resizes in place. allocRemaining now treats its limit as inclusive. readFileToEnd keeps rejecting a file of exactly max_bytes by reading through appendRemaining. Direct allocRemaining callers for remote responses now accept a body exactly at their limit. Zig 0.17's memset stores a full vector per iteration, so the Linux memset replacement is removed. macOS release binaries now require macOS 15, Zig 0.17's default minimum.
The test only needs a current model and filler entries that share its prefix, so it no longer names a real model.
The compactor's allocation-failure test now backs its sweep with testing_allocator.no_resize. That file imports only std, so it belongs with the shared basics the boundary check already allows.
Zig 0.17's `zig build` no longer accepts --global-cache-dir, so the binary size builds and the PGSO pipeline set ZIG_GLOBAL_CACHE_DIR for the same directories. The binary size check now builds the base commit with the Zig version that commit pins in build.zig.zon. A pull request that changes the toolchain could not build its base with the new compiler, and the comparison now reports the size change across the toolchain bump.
Zig 0.17 sizes std.posix.termios on Linux to the kernel struct, but with libc linked std.posix.tcgetattr calls glibc, which writes its larger struct and overruns the caller's stack. ReleaseSafe builds crashed in the terminal launcher and on interactive startup. Use the TCGETS and TCSETS ioctls on Linux so the struct and the call share one layout. Other platforms keep the std.posix functions.
# Conflicts: # src/core/images/png_downscale.zig
Zig 0.17 compiles its build runner the first time zig build runs against an empty cache, which takes about 50 seconds. The macOS platform E2E job starts without a Zig cache, so the first MCP test that ran the dispatcher driver timed out and was killed before the cache was written, leaving every later test cold as well. Build the driver once in a beforeAll with a longer timeout through a new build-only step. The MCP auth tests also keep Zig's global cache when they isolate HOME, so local runs stop rebuilding it per test.
Zig 0.17 can number anonymous symbols differently on another runner, so the same commit emits bitcode that differs in a few symbol names. The candidate job rebuilt the bitcode and required it to match the seed byte for byte, which failed on every run. Copy the seed's hash-verified bitcode instead, the same way the job already reuses the seed's control binary and merged profile.
Zig 0.17 bundles the macOS 27 libSystem stub, which lists arm64e.x1 targets. The Xcode 16.4 linker on the macos-15 runners rejects that architecture, so the temporal candidate link failed. Link against a copy of the pinned stub with only those target entries removed. The arm64 link never resolves against them, and the evidence records the hashes of both stubs.
Zig 0.17 emits array bitcasts, such as the [4]u8 reads in std.mem.eql, as per-element loops that the optimizer later folds into single loads. Instrumentation runs before loop unrolling, so the training profile counted every element. On the e2e-cli and e2e-tui-resize shard, mem.eqlBytes rose from 1.5 billion counts to 22 billion while every other function stayed level. That one function then held most of the profile, so the cold cutoff marked nearly all other code minsize and a supplemented function landed exactly on the cutoff. Run sroa and loop-unroll-full ahead of the default pipeline when generating and using profiles. Both sides see the same IR at the instrumentation point, so CFG hashes still match, and eqlBytes gets the same counter layout it had on Zig 0.16.
Unrolling constant-trip loops ahead of the default pipeline removed the per-element counts, but the raw unrolled code also made the optimized benchmarks slower. The ui-activity candidate ran about 19% slower than its plain O2 control, and an unprofiled build with the same preparation was 11% slower on its own. Running instcombine before and after the unroll and simplifycfg after it brings that build within about 1% of plain O2, while eqlBytes keeps its Zig 0.16 counter layout.
Open src/main.zig relative to the build root handle instead of joining the root path by hand, so reading the version needs no separate allocation failure path.
pathForReexec already resolves the installed test binary from test_paths, so the test-only branch in foregroundSessionExecutable repeated it with an error arm that could no longer run. Drop it and keep testProductExe private to self_exe.
fazxes
marked this pull request as ready for review
October 6, 2026 13:15
Zig 0.17 removed the ** operator. The tall pasted prompt test that landed on main used it, so build its body with text_utils.repeat like the rest of the file.
Zig 0.17's @hasDecl only finds pub declarations. The host workspace test app that landed on main declared workspaceHostInfo without pub, so the agent runtime ignored its host root and two tests failed.
fazxes
marked this pull request as draft
October 7, 2026 05:03
fazxes
marked this pull request as ready for review
October 7, 2026 06:17
# Conflicts: # src/core/agent/runtime/parallel_execution.zig
The libfx durability work and the other changes that just landed on main were written for Zig 0.16. - Six tests built strings with the ** operator, which 0.17 removed. Build them with text_utils.repeat. - journal.zig built a std.json.Array from its fields. 0.17 added a pointer_stability field to that type, so use fromOwnedSlice, which sets the same fields. - A new allocation-failure sweep used std.testing.allocator. Use testing_allocator.no_resize like the other sweeps, so the allocation sequence is the same on every run.
fazxes
marked this pull request as draft
October 9, 2026 06:58
fazxes
marked this pull request as ready for review
October 9, 2026 07:59
Zig 0.17 deprecates standard library names that now forward to new declarations. Replace each use with the declaration it forwards to, so behavior does not change: - std.fmt.allocPrint(alloc, ...) becomes alloc.print(...), and std.fmt.bufPrint becomes std.mem.print. - The std.mem.indexOf and lastIndexOf families become std.mem.find, findScalar, findLast, and the rest of the find family. - std.fs.path and std.fs.max_path_bytes become std.Io.Dir.path and std.Io.Dir.max_path_bytes, and path.resolve and path.relative become resolveAlloc and relativeAlloc. - @import("builtin").os and .cpu become .target.os and .target.cpu. - std.builtin becomes std.lang, and the Unmanaged container aliases become std.ArrayList, std.array_hash_map.String, and std.bit_set.Dynamic. A script made these edits outside strings and comments and resolved each file's local aliases, so this commit contains only renames.
These replacements change more than a name, so each call site was checked by hand: - std.mem.copyForwards and copyBackwards become @memmove. @memmove requires equal lengths, so the six sites that copied into a longer destination now slice it to the source length. - std.unicode.utf8Decode becomes display_width.decodeUtf8Sequence, which checks the lead byte against the slice length before decoding. The deprecated decoder reached an assertion when they disagreed, so inline rendering crashed when a truncated UTF-8 sequence preceded an emphasis delimiter, on Zig 0.16 as well. A regression test covers that input, and inline_render.zig joins the unit test list. - builtin.mode becomes builtin.optimize. Zig 0.17 renamed its tags to debug, safe, fast, and small, so the trace report maps them back to the Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall labels it printed before. - std.debug.runtime_safety becomes builtin.optimize.runtimeSafety(), std.math.divCeil becomes @divCeil, and ArrayList.getLast becomes last().?.
AGENTS.md lists the Zig 0.17 standard library forms that fx uses in place of the declarations 0.17 deprecates, and its I/O section no longer refers to 0.16. Comments that described Zig 0.16 behavior now describe 0.17: Response.reader is unchanged, Writer.Allocating still reports allocation failure as WriteFailed, and Darwin preserve-rename calls renameatx_np but still needs fx's own error mapping. The web research eval prompt names Zig 0.17.
fazxes
marked this pull request as draft
October 9, 2026 10:04
Rename the two std.mem.lastIndexOfScalar calls that follow a slice range, which the earlier rename missed, to std.mem.findScalarLast. Correct the preserve-rename comment: Zig's errnoBug panics only in Debug builds and returns error.Unexpected otherwise, so ENOTEMPTY does not panic in ReleaseSafe. fx still needs its own error mapping.
# Conflicts: # src/builtins/mcp.zig # src/core/mcp/mcp_runtime.zig # src/core/mcp/project_config.zig # src/core/mcp/tool_search.zig
The MCP-v2 engine and the other changes that just landed on main were written for Zig 0.16. - Rename the APIs that 0.17 deprecates in the new code, with the same exact-alias renames as the rest of the branch: allocPrint and bufPrint, std.mem.indexOf* and lastIndexOf*, std.fs.path, and builtin.os. zig fmt rewrote @intFromEnum and @enumFromInt as @backingInt and @fromBackingInt. - Three tests built strings with the ** operator, which 0.17 removed. The MCP host test uses text_utils.repeat. The src/mcp tests use @Splat, because that engine imports nothing outside its own directory. - The MCP host menu filter called std.ascii.indexOfIgnoreCase, which 0.17 removed. Call findIgnoreCase, which it aliased. - Two event queues and a fuzz test shifted elements with copyForwards or copyBackwards. Their source and destination lengths already match, so use @memmove. The nine @hasDecl probes in the new code all name pub declarations, so the 0.17 rule that @hasDecl sees only pub declarations leaves them as they were.
Six lines carried over from main formatted bounded values into fixed buffers and asserted the result with catch unreachable. - handleKernelCheckpoint, emitInlineLink, and a menu test already return errors, so they propagate the result with try. - The MCP sign-in redirect URI is assembled from its parts with std.fmt.printInt. A comptime check proves the longest port fits the 64-byte buffer. - The pending blob name is built with std.fmt.bytesToHex, which cannot fail and produces the same name. - failureDiagnosticValue copies the code and detail into its buffer. The detail is already capped so the joined text fits. A new test checks the redirect URI for one-, four-, and five-digit ports.
# Conflicts: # README.md # src/gateway/host_stream_provider.zig
The Codex host stream that just landed on main converts the HTTP status with @enumFromInt, which 0.17 deprecates. Use @fromBackingInt with the same @intcast as the rest of the file, because the status code is a u16 and std.http.Status is backed by a narrower integer. The rewritten README still asked for Zig 0.16.0 to build fx. Ask for 0.17.0.
fazxes
marked this pull request as ready for review
October 11, 2026 06:50
# Conflicts: # src/core/app/app_mcp_runtime.zig # src/core/mcp_host/login.zig # src/mcp/auth/oauth.zig # src/ui/footer/mcp_host_menu_presentation.zig
The MCP-v2 fixes that just landed on main format with std.fmt.allocPrint and std.fmt.bufPrint, which 0.17 deprecates. Use alloc.print and std.mem.print, as the rest of the code does. A menu test builds a long name with **, which 0.17 removed. Build it with text_utils.repeat. The sign-in bridge formats its host header with bufPrint and catch unreachable. answerForm already returns errors, so use try.
fazxes
marked this pull request as draft
October 11, 2026 21:39
fazxes
marked this pull request as ready for review
October 12, 2026 01:12
# Conflicts: # src/core/execution/command_runner.zig # src/gateway/host_model_catalog.zig # src/gateway/host_stream_provider.zig # src/gateway/js_host_model_catalog.zig
The command environment and direct-command permission changes that just landed on main read builtin.os, which 0.17 deprecates. Read builtin.target.os instead. The new host HTTP status check converts with @enumFromInt, and its test reads the status back with @intFromEnum. Use @fromBackingInt with @intcast and @backingInt, as the rest of the gateway does.
# Conflicts: # src/core/mcp_host/login.zig # src/core/session_manager/log.zig
The request image budget that just landed on main formats with std.fmt.allocPrint, which 0.17 deprecates, and one of its agent tests builds a long model name with **, which 0.17 removed. Use the arena's print and text_utils.repeat. The catch unreachable cleanup changed the sign-in bridge's host header to try std.fmt.bufPrint and gates the log's batch check on std.debug.runtime_safety. Use std.mem.print and builtin.optimize.runtimeSafety().
fazxes
marked this pull request as draft
October 12, 2026 03:26
The gateway request watch that just landed on main explains its design in terms of Zig 0.16. Select and its queue are unchanged in 0.17, and cancelling a still-waiting watch signals its thread the same way, so name the version fx now builds with.
fazxes
marked this pull request as ready for review
October 12, 2026 04:38
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Build fx with Zig 0.17.0.
build.zig.zonrequires it, and every CI, release, benchmark, and libfx workflow installs it.Require macOS 15 or later for the macOS release binaries, which is Zig 0.17's default minimum. Binaries built with Zig 0.16 ran on macOS 13.
Accept MCP authorization documents, gateway generation responses, and provider error bodies that are exactly at their size limit, because Zig 0.17's
allocRemainingtreats the limit as inclusive.readFileToEndstill rejects a file of exactlymax_bytes.Use Zig's
memsetin Linux binaries in place of fx's replacement, now that Zig's version stores a full vector per iteration.Qualify PGSO builds with LLVM 22.1.8, the LLVM version in Zig 0.17.
Run allocation-failure sweeps and allocation-count tests on an allocator that never resizes in place, so they make the same allocations on every run under Zig 0.17's
SafeAllocator.Read and set terminal attributes with the kernel ioctls on Linux. Zig 0.17 sizes
std.posix.termiosto the kernel struct, butstd.posix.tcgetattrcalls glibc when libc is linked, and glibc writes its larger struct past the end of it. ReleaseSafe Linux builds crashed in the terminal launcher and on interactive startup.Build the MCP stdio dispatcher driver once before its E2E tests. Zig 0.17 spends about 50 seconds compiling its build runner against an empty cache, which is longer than the first test's timeout in the uncached macOS platform job.
Stop using every standard library declaration that Zig 0.17 deprecates, such as
std.fmt.allocPrint, thestd.mem.indexOffamily,std.fs.path, and@import("builtin").os. Most are exact renames.std.mem.copyForwardsbecame@memmove, with each destination sliced to its source's length where it was longer, and the trace report mapsbuiltin.optimizeback to theReleaseSafestyle labels it printed before. The one deprecated call left isb.addTranslateC, because its replacement is the external translate-c package.Decode single UTF-8 sequences with a helper that checks the lead byte against the slice length. The deprecated
std.unicode.utf8Decodereached an assertion when they disagreed, so inline rendering crashed when a truncated sequence preceded an emphasis delimiter.Profile the seed's verified bitcode in the distributed PGSO candidate job instead of rebuilding it. Zig 0.17 can number anonymous symbols differently on another runner, so a rebuild of the same commit no longer matches byte for byte.
Link the PGSO candidate against a copy of Zig's macOS 27
libSystem.tbdwithout itsarm64e.x1targets. The Xcode 16.4 linker on the macos-15 runners rejects that architecture, and the arm64 link never uses it.Unroll and fold constant-trip loops before PGSO instrumentation and profile use. Zig 0.17 emits array bitcasts as per-element loops, so the training profile counted every element and
mem.eqlBytesdominated it, which pushed nearly all other code below the cold cutoff.