A fuzz test reads its input through std.testing.Smith, which reads a slice
as a little-endian u32 length and then that many bytes. The inline seeds of
the older fuzz tests were plain strings, so each lost its first four bytes
to the length: "one" replayed as an empty input, and "split every field"
read "spli" as a length. The fx tests now wrap their seeds in
fuzz_seed.slices, which writes that length first. The session manager
imports nothing from fx, so its two targets keep their seeds as saved
inputs in tests/fuzz/corpus.
ThreadSanitizer does not intercept mremap, so when std.heap.PageAllocator shrinks or moves a block on Linux, the released pages keep their access history and the next thread to use those addresses appears to race with the one that released them. That produced the race reports in the MCP HTTP large-answer test. Sanitized x86_64 and aarch64 Linux builds now link an mremap that makes the same change with munmap, and with mmap, a copy, and munmap when a block moves.
- `zig build fuzz` builds only the fuzz tests listed in `tests/fuzz/targets.json`. `-Dfuzz-target=<name>` picks one target, and `--fuzz=N` searches for new failing inputs.
- `zig build test` replays each target's saved inputs from `tests/fuzz/corpus/<target>/` on every pull request.
- A nightly Fuzz workflow fuzzes every target for about 20 minutes, uploads any failing input, and opens or updates an issue for that target. A pull request that changes the fuzzing setup fuzzes each target for two minutes.
- The shell parity tests run every command form fx runs without review through sh, dash, bash, and zsh, with and without `extendedglob`, and check that each one runs exactly as fx parsed it. The `shell_parity` target checks generated commands the same way.
Three host files, the approval review benchmark, and the session manager's
hooks-only tests were never compiled into a `zig build test` binary. Their
35 tests never ran, locally or in CI, and 5 more always skipped.
- The host files that only the browser build uses join `main.zig`'s test
block, next to `js_host_workspace.zig`.
- The approval review benchmark's tests join the `test` step, as the
activity benchmark's already do.
- A second session manager test build, with its fault and trace hooks
compiled in, joins `test` and `test-session-manager`. Shipped artifacts
still leave the hooks out.
Every MCP server now runs on the engine in src/mcp. The v1 client is gone,
with its stdio dispatcher, feature catalogs, HTTP+SSE transport and Docker
cleanup. The config parsing and shared contracts the new host still uses stay
in src/core/mcp.
Also:
- An mcp.json with both "mcp" and "mcpServers" notes each server it skips.
- An ACP session that names two MCP servers alike is refused at session/new.
- A server an editor supplies gets the editor's sign-in hint.
- /mcp logout says a server wasn't signed in when no login was saved.
- `libfx.node` and `fx-core.wasm` no longer contain the ACP E2E test controls, which wrote marker files and waited on a release file when their `FX_E2E_ACP_PROMPT_*` variables were set. The fx binary keeps them for its E2E tests.
- The libfx builds no longer compile `src/main.zig`. The terminal UI reads the version from the build options, which `build.zig` takes from the same `pub const version` in `src/main.zig`, so a change to the CLI's composition root can no longer break a libfx build.
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.
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.
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.
- Add --sessions-v2 to fx ask (before or after ask) and FX_SESSIONS_V2=1. With either, fx ask saves the session as an append-only log in ~/.fx/sessions/v2/<id>/.
- Resume those sessions with --resume last and --resume-id; they are only visible with the flag on. --no-save still writes nothing.
- Save each finished exchange while the turn runs. After a crash, the prompt and finished tool calls are kept, and the next resume marks that turn interrupted.
- Store tool output side files in ~/.fx/session-files/<id>/, owner-only like the log.
- On resume, cut a half-written last line. A damaged line elsewhere stops the resume with InvalidSessionFormat and leaves the file unchanged. A session another process holds fails with SessionBusy.
- Report a missing session with the current store's errors, NoSavedSessions and SessionNotFound.
- Recover profile usage from v2 sessions after a crash, as for current sessions.
- Leave subagents off in fx ask while the flag is on.
- Keep the current store as the default; it now ignores the v2 folder.
Use build-time fatal diagnostics when automatic Node header discovery cannot run or allocate its result. This keeps expected configuration failures out of panic paths.
Add a Node-API addon for running headless fx agents inside Node.js while keeping fetch streaming and cancellation in JavaScript.
Expose Node and browser package entry points with WebAssembly fallback and terminal support. Build, test, and package native macOS/Linux addons alongside ReleaseSmall WebAssembly artifacts.