The Codex native-cache fixture materialized its cache with
`std::fs::copy`, while the byte-identical Claude fixture copies explicit
bytes. Across the seven Linux CI runs in this fleet, the Claude fixture
passed 7/7 and the Codex fixture failed 2/7, always with exactly one
cache file differing by content only (same length, different sha256) at
a file the fixture had just copied.
`std::fs::copy` does not copy bytes in-process: on Linux `io::copy`
moves data between the file descriptors with `copy_file_range(2)`,
`sendfile(2)`, or `splice(2)` (std 1.99.1,
`library/alloc/src/io/copy.rs`), while macOS clones or `copyfile(3)`s
(`library/std/src/sys/fs/unix.rs`). A byte-level defect in the CI
filesystem's offload path is therefore invisible on macOS. The Claude
fixture already hit this and was fixed in 03212da ("test(hosts):
avoid Linux native cache copy offload"); the Codex fixture kept the
crate's last `std::fs::copy`.
Materialize both hosts' caches through one byte-level fixture authority,
assert the refreshed source and cache bytes at the refresh site, and pin
that a same-size byte change with an unchanged mtime still deactivates
the plugin. The activation probe was already content-based
(`bundle_identity.rs`), so no product code changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
//crates/tracedecay-agent-hosts:unit_testfailed twice on unrelated PRs (Linux CI, 2026-10-31) inagents::codex::tests::native_cache_content_drift_and_binary_relocation_require_refresh: after the test refreshedthe Codex plugin cache, exactly one cache file differed by content only (same length, different sha256), so the
activation probe correctly refused to call the plugin active.
Verified root cause
codex_loaded_cache_matches_rendered_bundlecomparesrendered, source, and cache content digests (
crates/tracedecay-agent-hosts/src/agents/bundle_identity.rs:26-58,agents/codex.rs:1454-1495), and every refresh write publishes bytes(
agents/codex.rs:775-786,agents/text_file_transaction.rs:473-511). The corrupted skill text is static(
plugin/skills/assessing-impact/SKILL.mdis embedded at build time), so "refresh skipped an unchanged-size file"cannot explain it.
std::fs::copy(crates/tracedecay-agent-hosts/src/agents/codex/tests.rs:773atb989d1f6e5), while theotherwise equivalent Claude fixture copies explicit bytes.
failed 2/7 (
/tmp/td-ci/4668,/tmp/td-ci/4669). Each failure named exactly one file the fixture had justcopied —
cache/skills/assessing-impact/SKILL.mdand, in the other run,cache/.mcp.json— with identicallength and a different digest. The skill's bytes were identical before the refresh, so the copy itself produced
the wrong bytes, not a stale render.
std::fs::copyis not an in-process byte copy. On Linuxio::copymoves data between thefile descriptors with
copy_file_range(2)/sendfile(2)/splice(2)(pinned std 1.99.1,library/alloc/src/io/copy.rs:73-84); macOS clones orcopyfile(3)s(
library/std/src/sys/fs/unix.rs:2202-2249). The same fixture therefore passes on macOS.03212da800("test(hosts): avoid Linux native cache copy offload", merged in Bound storage-report sizing to a shared filesystem budget #4353). The Codex fixture kept the crate's last
std::fs::copy.Not reproduced locally. macOS is green before and after (pre-fix run, then
--runs_per_test=3); no Linux hostwas available. The evidence is the 7-run cross-host comparison plus the repository's earlier identical fix.
Fix
agents/native_cache_fixture.rsis the single byte-level authority both host fixtures use to materialize anative cache (
std::fs::read+std::fs::write, neverstd::fs::copy), with the reason recorded so a futurefixture cannot reintroduce the offload path.
assert_rendered_bundle_bytesnow runs inside the Codex test immediately after the relocation refresh, so a cachedivergence fails at the refresh site naming the file and both
len/sha256values instead of surfacing as anopaque activation failure. The Claude test uses the same assertion.
native_cache_same_size_unchanged_mtime_content_drift_deactivatespins that activation is content-based: asame-length byte change with the original mtime restored still deactivates the plugin.
Evidence
bazel test //crates/tracedecay-agent-hosts:unit_test --runs_per_test=3 --nocache_test_results→ 3/3 PASSED(561 passed, 0 failed, 1 ignored per run), including both refresh fixtures and the new drift test.
bazel build --config=clippy //crates/tracedecay-agent-hosts:unit_test→ clean with-Dwarnings.bazel build //crates/tracedecay-agent-hosts:rustfmt_check_2024→ clean.bazel run //scripts:gen_builds -- --check→ "all 61 generated BUILD.bazel files are current".🤖 Generated with Claude Code