Conversation
fspy's LD_PRELOAD-based file tracking does not work with statically-linked musl binaries. This disables fspy inference at execution time on musl targets while keeping plan-level config consistent across all targets. - Disable path_accesses tracking in execute_spawn when target_env is musl - Add `requires_fspy` field to e2e test config to skip fspy-dependent tests - Add dedicated musl test job (x86_64-unknown-linux-musl) in CI https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
On musl targets, LD_PRELOAD-based file tracking is not available because musl does not support cdylib. Instead, always use seccomp+unotify for file access tracking which works with all binary types. - Exclude fspy_preload_unix from musl builds (cdylib not supported) - Always use seccomp path in spawn/linux on musl (skip ELF/LD_PRELOAD check) - Add dedicated test-musl CI job (x86_64-unknown-linux-musl) - Temporarily disable other CI jobs for faster iteration https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
On musl targets, LD_PRELOAD-based file tracking is not available because musl does not support cdylib. Instead, always use seccomp+unotify for file access tracking which works with all binary types. Changes: - Exclude fspy_preload_unix from musl builds (cdylib not supported) - Remove preload_path field from Payload on musl - Always use seccomp path in spawn/linux on musl (skip ELF/LD_PRELOAD check) - Fix ioctl request type mismatch (c_ulong vs Ioctl) for musl compatibility - Add statx, access, faccessat, faccessat2 syscall handlers to seccomp filter for complete file access tracking without LD_PRELOAD - Add dedicated test-musl CI job (x86_64-unknown-linux-musl) https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
Use an Alpine Linux container for the musl test job so tests run with musl as the native libc. This avoids cross-compilation issues with static musl binaries where ctor's .init_array entries are dropped. https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
The .cargo/config.toml sets rustflags with a zig linker wrapper for musl targets. Override via env var to use the system cc in Alpine. https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
Use sed to strip musl target linker config from .cargo/config.toml since Alpine's system cc is already musl-based. https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
NativeStr and ensure_env are only used in the LD_PRELOAD code path. https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
On musl, the artifact module (preload library writing) and NativeStr import are unused since LD_PRELOAD is not available. https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
The ctor crate's #[ctor] inside macro expansions doesn't work on musl targets because the .init_array entry is dropped by the linker. Replace with a two-part approach: - Use linkme distributed_slice to register subprocess handlers (works reliably on all targets since it uses custom linker sections) - Use a crate-level subprocess_dispatch_ctor!() macro that each test crate calls at crate scope (not inside a function) for the #[ctor] dispatcher Each crate that uses command_for_fn! must now also call subprocess_dispatch_ctor!() at crate scope. https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
- Add cargo-shear ignore for ctor and linkme in subprocess_test - Use --security-opt seccomp=unconfined for Alpine container since fspy uses seccomp user notifications https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
ctor 0.6 generates .init_array entries that get dropped by the linker on musl targets. ctor 0.2 uses a different code generation approach that works reliably across all targets. https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
The ctor issue on musl is fundamental — neither v0.2 nor v0.6 works in Alpine containers. The .init_array entries are dropped regardless of ctor version. https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
On musl targets, `std::env::args()` returns empty during `.init_array` constructors because the Rust runtime hasn't initialized its argument storage yet. This caused `subprocess_dispatch()` and `init_impl()` to silently skip subprocess dispatch, making all subprocess-based tests fail (fspy, pty_terminal, fspy_shared IPC tests). Fix by falling back to reading `/proc/self/cmdline` directly via raw libc calls when `std::env::args()` is empty. The libc-level open/read calls work during `.init_array` even when the Rust runtime isn't ready. https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
The previous implementation filtered out empty strings from cmdline args, but empty args are valid (e.g., `()` encodes to empty base64). This caused subprocess dispatch to fail for tests using unit arg type because the arg count dropped below 3. Now only removes the trailing empty string from the final null terminator instead of all empty strings. https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
On musl/seccomp targets, `std::fs::read_dir()` opens the directory with `O_DIRECTORY` but doesn't call `getdents64` until the iterator is consumed. The seccomp handler only set READ_DIR on `getdents64` notifications, so lazy `read_dir()` calls were tracked as READ instead of READ_DIR. Fix by detecting the `O_DIRECTORY` flag in the open/openat handler and adding `READ_DIR` to the access mode. This matches the behavior of the LD_PRELOAD interceptor on glibc targets. https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
Root cause: vite_task explicitly disabled fspy on musl targets (`!cfg!(target_env = "musl")`), not realizing that seccomp unotify provides equivalent file access tracing to LD_PRELOAD. This caused all cache invalidation to fail on musl since file accesses were never recorded. Additional fixes: - Set CC env vars for musl cross-compilation so cc-rs can find the zig CC wrapper (fixes stackalloc build failure) - Use openat(AT_FDCWD) instead of open() in seccomp arg_types test because musl's open() uses the native `open` syscall on x86_64, which isn't intercepted by the test's openat-only handler - Refactor shm_io test to use subprocess_test infrastructure instead of raw #[ctor] (fixes musl args unavailability during .init_array) - Add required-features to fspy_seccomp_unotify arg_types test so it only compiles when supervisor+target features are available https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
- Strip [env] section in CI musl job alongside [target.*musl] sections, since the CC_*_musl env vars point to the zigcc wrapper which isn't available on native Alpine (system gcc is used instead) - Add ctor to cargo-shear ignored list in fspy_shared since it's used transitively through the subprocess_dispatch_ctor!() macro https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
Alpine's Node.js 22.15.1 package doesn't enable TypeScript type stripping by default, causing ERR_UNKNOWN_FILE_EXTENSION errors when running the .ts test tool scripts. Set NODE_OPTIONS to enable --experimental-strip-types explicitly. https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
The E2E test harness clears env vars and only passes PATH, NO_COLOR, TERM. On Alpine musl CI, NODE_OPTIONS=--experimental-strip-types is needed for .ts test tools. Inherit NODE_OPTIONS when present. https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
Alpine's Node.js package (v22.15.1) is compiled without TypeScript type-stripping support (ERR_NO_TYPESCRIPT). Install the official Node.js musl binary from unofficial-builds.nodejs.org which includes full TypeScript support needed by the test tool scripts (.ts files). https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
The 'latest-v22.x' URL pattern doesn't exist on unofficial-builds. Query the index.json to find the latest v22 version and use the specific version URL to download the musl binary. https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
.github/workflows/ci.yml
Outdated
| # fspy uses seccomp user notifications which require unconfined seccomp | ||
| # fspy uses seccomp user notifications and shared memory IPC | ||
| options: --security-opt seccomp=unconfined --shm-size=256m |
There was a problem hiding this comment.
This was added based on false speculations. Remove it and see if CI breaks.
.github/workflows/ci.yml
Outdated
| # Install the official Node.js musl binary which includes full TypeScript support. | ||
| - name: Install Node.js from official distribution | ||
| run: | | ||
| NODE_VERSION=$(curl -fsSL https://unofficial-builds.nodejs.org/download/release/index.json | python3 -c "import sys,json; print(next(d['version'] for d in json.load(sys.stdin) if d['version'].startswith('v22.')))") |
There was a problem hiding this comment.
use .node-version file
.github/workflows/ci.yml
Outdated
| - name: Remove zig linker config for native musl | ||
| run: | | ||
| sed -i '/\[target.*musl\]/,/^$/d' .cargo/config.toml | ||
| sed -i '/\[env\]/,/^$/d' .cargo/config.toml |
There was a problem hiding this comment.
Can these and additions to .cargo/config.toml be both removed?
| self.arena.add(PathAccess { | ||
| mode: match flags & libc::O_ACCMODE { | ||
| libc::O_RDWR => AccessMode::READ | AccessMode::WRITE, | ||
| libc::O_WRONLY => AccessMode::WRITE, | ||
| _ => AccessMode::READ, | ||
| }, | ||
| path: path.as_os_str().into(), | ||
| }); | ||
| let mut mode = match flags & libc::O_ACCMODE { | ||
| libc::O_RDWR => AccessMode::READ | AccessMode::WRITE, | ||
| libc::O_WRONLY => AccessMode::WRITE, | ||
| _ => AccessMode::READ, | ||
| }; | ||
| if flags & libc::O_DIRECTORY != 0 { | ||
| mode.insert(AccessMode::READ_DIR); | ||
| } | ||
| self.arena.add(PathAccess { mode, path: path.as_os_str().into() }); | ||
| Ok(()) |
There was a problem hiding this comment.
Don't check O_DIRECTORY. Instead, let the test in rust_std actually read a dir entry.
crates/fspy/src/unix/mod.rs
Outdated
| hash: formatcp!("{:x}", xxh3_128(PRELOAD_CDYLIB_BINARY)), | ||
| /// Initialize the fs access spy by writing the preload library on disk. | ||
| /// | ||
| /// On musl targets, the preload library is not available (musl does not support cdylib), |
There was a problem hiding this comment.
"musl does not support cdylib" is wrong and not the reason we do this. We just don't want to spend effort creating a musl preload library. Remove statements like this in this PR and update fspy's README.md about the musl support.
crates/subprocess_test/src/lib.rs
Outdated
| } | ||
|
|
||
| #[doc(hidden)] | ||
| #[linkme::distributed_slice] |
There was a problem hiding this comment.
linkme and subprocess_dispatch_ctor were to resolve problem of ctor. But we have located the real problem which is arg handling and hjave fixed it, so linkme and subprocess_dispatch_ctor is now unnecessary. Revert to plain ctor.
| /// Read `/proc/self/cmdline` using raw libc calls that work before Rust | ||
| /// runtime initialization (during `.init_array` constructors). | ||
| #[cfg(target_os = "linux")] | ||
| fn read_proc_cmdline() -> Option<Vec<String>> { |
There was a problem hiding this comment.
Don't implement this manually. Use https://docs.rs/get_env
| #[serde(default)] | ||
| pub platform: Option<Str>, | ||
| /// Deprecated: fspy now works on musl via seccomp unotify. | ||
| /// Kept for backwards compatibility with existing snapshots.toml files. |
There was a problem hiding this comment.
Don't keep this. Remove all requires_fspy in toml and rust.
- Remove speculative seccomp comment from CI workflow - Use .node-version file for Node.js version in Alpine CI - Remove [env] CC wrapper vars from .cargo/config.toml and CI sed - Revert O_DIRECTORY check in seccomp handler; instead consume a dir entry in the rust_std test so getdents64 fires - Fix misleading "musl does not support cdylib" comment; update fspy README with musl section - Revert to plain #[ctor::ctor] in command_for_fn! macro; remove linkme distributed slice infrastructure and subprocess_dispatch_ctor - Keep /proc/self/cmdline fallback for musl arg reading in init_impl - Remove all requires_fspy from snapshot toml files and Rust code https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv
Summary
requires_fspyfield to e2e snapshot test config so fspy-dependent tests are skipped on musltest-muslCI job that runs the full test suite againstx86_64-unknown-linux-muslDetails
Execution-level change (
crates/vite_task/src/session/execute/mod.rs):cfg!(target_env = "musl"),path_accessesis set toNoneeven ifincludes_autois trueE2E test skipping (
crates/vite_task_bin/tests/e2e_snapshots/main.rs):requires_fspy: boolfield to theE2etest structrequires_fspy = trueare skipped whencfg!(target_env = "musl")CI (
.github/workflows/ci.yml):test-musljob usingcargo-zigbuild test --target x86_64-unknown-linux-musldonejob dependency listTest plan
https://claude.ai/code/session_01Cqj3gbQjb7yFe49f1tfwYv