Repository navigation
Description
What variant of Codex are you using?
Rust CLI source, specifically the isolated codex-git-utils platform module at 8c076942cb70fba8e5c4732c23f1faabc4aed484.
What feature would you like to see?
Please add WASI symlink creation to codex-rs/git-utils/src/platform.rs. The current guard rejects WASI because it is neither Unix nor Windows:
error: codex-git symlink support is only implemented for Unix and Windows
--> src/platform.rs:37:1
|
37 | compile_error!("codex-git symlink support is only implemented for Unix and Windows");
I reproduced this by compiling the unmodified platform module and its actual error type for wasm32-wasip1 with stable Rust 1.95.0. WASI provides symlink creation, so this particular rejection can be removed without stubbing behavior.
Additional information
A reference implementation is available on the public fork branch, at commit 84b7ecac53dec547820c2fd5bae94c1f02ce0861 (diff from the base). This follows the issue-based contribution path; there is no upstream PR.
The change adds a target_os = "wasi" arm using libc::symlink, with a WASI-only dependency on the existing locked libc 0.2.186. It converts paths to C strings, rejects embedded NULs with InvalidInput, preserves relative link text, and propagates errno through GitToolingError::Io. Unix and Windows implementations are unchanged.
Prior art uses std::os::wasi::fs::symlink_path with feature(wasi_ext). Stable Rust 1.95.0 rejects that API with E0658; the libc implementation avoids a nightly requirement.
Validation
The focused harness imports the candidate's actual platform.rs and errors.rs, without rewriting their source or substituting an error type. At the exact candidate above:
- Native macOS ARM64, stable Rust 1.95.0: four tests passed.
wasm32-wasip1, stable Rust 1.95.0: compiled and linked; the same four tests passed in Node 24.18.0's WASI preview1 runtime.wasm32-wasip2, stable Rust 1.95.0: compiled and linked; runtime behavior was not tested.- The tests cover relative target resolution, dangling links that later resolve to directories, preservation of an existing destination, and NUL rejection in either path.
- Scoped rustfmt and diff whitespace checks passed. Independent GPT 6.1 Sol xhigh code review of this base/candidate found no actionable findings; the reviewer inspected the source and did not rerun tests.
The full package test did not reach compilation in the offline environment because the pinned rmcp Git dependency was not cached. These results establish module behavior only. They do not establish a complete codex-git-utils or Codex WASI build. Other WASI portability work remains outside this change.
Reproduce the focused module proof
Requires Rust 1.95.0, its WASI target standard libraries, and Node with node:wasi.
git clone --branch fix/git-utils-wasi-symlink https://github.com/JustYannicc/codex.git
cd codex
git checkout 84b7ecac53dec547820c2fd5bae94c1f02ce0861
codex_source="$PWD"
proof_dir="$(mktemp -d)"
mkdir -p "$proof_dir/src"
cat > "$proof_dir/Cargo.toml" <<'EOF'
[package]
name = "codex-wasi-platform-proof"
version = "0.0.0"
edition = "2024"
[dependencies]
libc = "=0.2.186"
thiserror = "=2.0.18"
walkdir = "=2.5.0"
[dev-dependencies]
tempfile = "=3.27.0"
EOF
cat > "$proof_dir/src/lib.rs" <<EOF
#[path = "$codex_source/codex-rs/git-utils/src/errors.rs"]
mod errors;
pub use errors::GitToolingError;
#[path = "$codex_source/codex-rs/git-utils/src/platform.rs"]
mod platform;
pub use platform::create_symlink;
EOF
rustup target add --toolchain 1.95.0 wasm32-wasip1 wasm32-wasip2
cargo +1.95.0 test --manifest-path "$proof_dir/Cargo.toml" -j 2
cargo +1.95.0 test --manifest-path "$proof_dir/Cargo.toml" --target wasm32-wasip1 --no-run -j 2
cargo +1.95.0 test --manifest-path "$proof_dir/Cargo.toml" --target wasm32-wasip2 --no-run -j 2
node --input-type=module - "$proof_dir" <<'JS'
import { WASI } from 'node:wasi';
import { readFile, readdir, mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
const deps = join(process.argv[2], 'target/wasm32-wasip1/debug/deps');
const binary = (await readdir(deps)).find(name => name.endsWith('.wasm'));
if (!binary) throw new Error('WASI test binary not found');
const sandbox = await mkdtemp(join(tmpdir(), 'codex-wasi-symlink-'));
try {
const wasm = join(deps, binary);
const wasi = new WASI({
version: 'preview1',
args: [wasm, '--test-threads=1', '--nocapture'],
env: {},
preopens: { '/': sandbox },
});
const module = await WebAssembly.compile(await readFile(wasm));
const instance = await WebAssembly.instantiate(module, wasi.getImportObject());
process.exitCode = wasi.start(instance);
} finally {
await rm(sandbox, { recursive: true, force: true });
}
JSBoth native and WASIp1 runs reported:
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
The tests use tempfile::tempdir_in(".") because this stable WASI target's std::env::temp_dir() panics. The WASI runtime exposes only a fresh temporary directory as its working directory.
Activity
JustYannicc commentedon Oct 18, 2026
Follow-up evidence for the remaining WASI platform arms and a focused wasm CI lane. The symlink implementation above remains a separate, narrow fix; it does not make the full workspace WASI-compatible.
At current
main8c076942cb70fba8e5c4732c23f1faabc4aed484, the unchangedcodex-utils-path-uricrate fails on stable Rust 1.95.0 /wasm32-wasip1with five errors:The last error appears at three call sites.
cfg(unix)andcfg(windows)leave these declarations absent on WASI, which is neither. The relevant path-uri sources are byte-identical in officialrust-v0.162.1(092d3acd6bec3e3a14bdc7e7a2810ab628ab759d).Standalone reproduction and full compiler diagnostics isolate the actual
path-uriandabsolute-pathsources without substituting implementations or pulling the app-server's unrelated Git dependencies. The script verifies copied source bytes, retains the official Cargo lockfile and lifts only the workspace manifests:The focused path-utility fix is to enable the POSIX
PathConvention::nativearm on WASI and usestd::os::wasi::ffi::{OsStrExt, OsStringExt}in the two byte conversion branches. This compiles on stable without a new feature gate. There is a separate dependency caveat: lockedurl 2.5.8usesto_string_lossyon WASI. Simply adding the Codex cfg arms silently changes an invalid0xffpath byte into U+FFFD. servo/rust-url#1158 instead returnsErr, letting Codex use its existing opaque byte fallback. With that fix and the three cfg arms, four focused WASIp1 runtime checks pass: ASCII/special characters, native path convention, invalid bytes through the opaque fallback, and embedded NUL through the opaque fallback. Ordinary%FFfile-URI decoding remains unsupported.The rest of the downstream compile inventory is below. These are separate changes to assess, not a request to accept host-specific behavior wholesale:
arg0PATH_SEPARATORdeclaration has only Unix/Windows arms. Add a WASI POSIX arm, with target-specific PATH behavior tested.config/src/loaderexec-server/src/file_handleFileExtand testing offsets.exec-server/src/no_followstate/src/sqlite/validationfile-idlacks a WASI backend. A WASI dev/inode implementation needs identity tests.rmcp-clientandutils/path-utilsudshttp-clientapp-server/coreshutdown and exit statussiginfo_taccessors unavailable for this target. These are distinct from WASI's missing cfg declarations.For CI, an initial useful lane is the two path crates plus
git-utilson stablewasm32-wasip1: targetcargo checkfirst, then Node WASI tests for the small filesystem/path modules. WASIp2 can start as a separate compile/link lane. Once those packages compile directly in the workspace, CI should use their real manifests rather than the isolated diagnostic workspace above. A fullcodex-corewasm job should follow only after dependency, process and transport requirements are explicit; a compile or--versionsmoke must not be presented as a complete agent turn.No external Codex PR is being opened, in accordance with
docs/contributing.md. Downstream compatibility work continues independently of upstream triage.