Skip to content

bug(macos): macOS portability failures the Intel test lane surfaced #283

Description

@zackees

Context

#281 restored the Intel macOS test lane, which had been red for its entire life (14 runs, 14 failures, zero successes). The restored lane is the first thing in this repository to run the test suite on macOS: rust-native's matrix is [ubuntu-latest, windows-latest], macos-arm-tests is gated off by ENABLE_MACOS_RUNNERS, and the two Apple-hosted matrices in ci.yml run only the compiler-policy and screenshot proofs.

So the first green-ish run (35125870261) ran 973 tests on x86_64-apple-darwin and 23 failed. Diagnosing them split the set in two: environment artifacts of a Recovery guest, and genuine macOS portability findings that would fail on any macOS host and had simply never been exercised.

The artifacts are excluded by name in ci/macos-x64/recovery-guest.sh with the evidence recorded inline and in ci/macos-x64/README.md. This issue is for the ones that are real. They are currently excluded from the lane too — otherwise the lane cannot be green — but excluding them is a deferral, not a resolution, and nothing else in CI will catch them.

Findings

1. cap-primitives 4.0.3 panics on a negative st_rdev on macOS — 12 tests

The largest group, and the only one caused by a dependency rather than by a test.

called `Result::unwrap()` on an `Err` value: TryFromIntError(())
  at cap-primitives-4.0.3/src/rustix/fs/metadata_ext.rs:171

Line 171 is:

rdev: u64::try_from(stat.st_rdev).unwrap(),

Two lines above it, the same struct handles the identical problem correctly:

dev: if stat.st_dev < 0 {
    i64::try_from(stat.st_dev).unwrap() as u64
} else {
    u64::try_from(stat.st_dev).unwrap()
},

That guard exists because dev_t is signed on macOS (i32) and unsigned on Linux. The author knew; rdev just never got the same treatment. On Linux the branch is dead, which is exactly why only a macOS run exposes it. rustix's libc backend declares pub st_rdev: c::dev_t, so the conversion is genuinely fallible.

No fixed release exists — cap-primitives 4.0.3 is the newest 4.x on crates.io (the next entries are 3.4.6, a backport on the older line), so this cannot be resolved by a version bump.

Affected: the 12 wasm::worker::output::tests / wasm::worker::tests cases exercised through the capability-based filesystem.

2. macOS rejects the TLS test fixtures' certificates — 2 tests

reqwest::Error { kind: Request, source: ... Error { code: -67901,
  message: "The validity period in the certificate exceeds the maximum allowed." } }

macOS's Security framework enforces a maximum TLS certificate validity (825 days) that the fixtures' self-signed certificates exceed. trusted_local_tls_preserves_body and trusted_https_downgrade_is_rejected_before_plaintext_connection are not #[ignore]d, so they run anywhere the suite runs on macOS.

3. macOS canonicalizes /var to /private/var — 1 test

assertion `left == right` failed
  left:  "/private/var/folders/zz/.../T/.tmpvPapPF/target"
  right: "/var/folders/zz/.../T/.tmpvPapPF/target"

context_metadata_and_link_read_do_not_follow_final_symlinks compares a path it built against a canonicalized one. /var is a symlink to /private/var on macOS, so the two differ. Linux and Windows have no equivalent alias, which is why this is macOS-only.

4. macOS returns ENOTSUP for the readiness marker's rename — 2 tests

Os { code: 45, kind: Uncategorized, message: "Operation not supported" }

failed_marker_write_is_cleaned_up_and_existing_marker_is_preserved and marker_is_invisible_until_payload_is_complete. Both depend on an atomic no-replace rename for the marker's publish step. Linux's renameat2(RENAME_NOREPLACE) has no drop-in macOS equivalent, and the call is returning ENOTSUP rather than falling back.

This one is worth prioritising: it is the same publish-atomicity property that the wasm::worker output path relies on, so a no-op fallback would be a correctness problem rather than a test-only one.

  • Confirm whether the production path reaches the same call, and whether a real Intel/ARM Mac returns ENOTSUP here or the guest's APFS does. — production: no, persist_noclobber has no production caller. The real-hardware half stays open as the checkbox in the correction comment below.
  • If production is affected, this is a bug, not a test fix. — n/a.

5. APFS rejects invalid-UTF-8 file names with EILSEQ — 1 test

Os { code: 92, kind: Uncategorized, message: "Illegal byte sequence" }

tree_hash::invalid_utf8_file_names_fail_instead_of_colliding creates a file with invalid UTF-8 in its name and asserts the tree hash fails rather than colliding. On Linux arbitrary bytes are legal; on APFS the name cannot be created at all, so the test fails one step earlier than it asserts. The tree_hash property still needs to hold on macOS, it just cannot be provoked this way.

Open question

Findings 2–5 would also fail on aarch64 macOS. We cannot currently tell whether they are Intel-specific, because macos-arm-tests is disabled. Once this lane has a green record it is worth deciding whether to flip ENABLE_MACOS_RUNNERS, which would give a second data point on the same tests.

Not in scope

EXCLUDE_ROOT (the guest runs as root, so a chmod 000 file is still readable), EXCLUDE_TTY (no controlling terminal), and EXCLUDE_VM_TIMING (two cores in a VM) are artifacts of the Recovery environment, not macOS findings, and are excluded for that reason.

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions