Skip to content

Support WASI symlink creation in codex-git-utils on stable Rust #55066

Open

Description

@JustYannicc

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 });
}
JS

Both 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

  1. JustYannicc commented on Oct 18, 2026

    @JustYannicc
    Author

    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 main 8c076942cb70fba8e5c4732c23f1faabc4aed484, the unchanged codex-utils-path-uri crate fails on stable Rust 1.95.0 / wasm32-wasip1 with five errors:

    E0425: cannot find value `path_bytes` in this scope
    E0425: cannot find value `decoded_path` in this scope
    E0599: no variant or associated item named `native` found for enum `PathConvention`
    

    The last error appears at three call sites. cfg(unix) and cfg(windows) leave these declarations absent on WASI, which is neither. The relevant path-uri sources are byte-identical in official rust-v0.162.1 (092d3acd6bec3e3a14bdc7e7a2810ab628ab759d).

    Standalone reproduction and full compiler diagnostics isolate the actual path-uri and absolute-path sources 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:

    git clone https://github.com/openai/codex.git
    cd codex
    git checkout 8c076942cb70fba8e5c4732c23f1faabc4aed484
    cd ..
    python3.11 codex-cfg-repro.py codex proof
    rustup target add --toolchain 1.95.0 wasm32-wasip1
    cargo +1.95.0 check --manifest-path proof/Cargo.toml -p codex-utils-path-uri --target wasm32-wasip1 -j 2

    The focused path-utility fix is to enable the POSIX PathConvention::native arm on WASI and use std::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: locked url 2.5.8 uses to_string_lossy on WASI. Simply adding the Codex cfg arms silently changes an invalid 0xff path byte into U+FFFD. servo/rust-url#1158 instead returns Err, 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 %FF file-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:

    Area Missing boundary or next focused change
    arg0 The PATH_SEPARATOR declaration has only Unix/Windows arms. Add a WASI POSIX arm, with target-specific PATH behavior tested.
    config/src/loader System config and requirements path declarations have only Unix/Windows arms. Make the WASI config-path policy explicit.
    exec-server/src/file_handle Add WASI positional read/write arms, using its platform FileExt and testing offsets.
    exec-server/src/no_follow Needs a WASI implementation with verified no-follow semantics. This must preserve link/race behavior, not follow links to make compilation pass.
    state/src/sqlite/validation file-id lacks a WASI backend. A WASI dev/inode implementation needs identity tests.
    rmcp-client and utils/path-utils Program resolution, inherited environment and system-command discovery need WASI decisions. Standard WASIp1 has no process-spawn ABI.
    uds Explicit unsupported errors are needed where the platform has no Unix sockets. A successful no-op is not socket support.
    http-client Native OpenSSL error typing/dependency must be excluded where the selected transport has no native TLS backend. Emscripten reaches this boundary too.
    app-server / core shutdown and exit status Signals, cancellation and process status need explicit platform semantics. Replacing signal waits with never-resolving futures does not establish parity.
    Emscripten telemetry and shell snapshots The pinned release passes blocking HTTP clients to OTLP where its wasm32 implementation is excluded, and uses siginfo_t accessors 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-utils on stable wasm32-wasip1: target cargo check first, 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 full codex-core wasm job should follow only after dependency, process and transport requirements are explicit; a compile or --version smoke 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.

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

    CLIIssues related to the Codex CLIenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions