How to Fix: Since Wasmtime 20, preopens of non-existent directories fail eagerly rather than on first access
Wasmtime 20 changed when WASI preopened directories are validated: paths that do not exist are now rejected immediately during startup instead of failing only when code first touches them. On Windows, that breaks packages such as yowasp-yosys that register optional search paths from a Python virtual environment and previously relied on lazy failure semantics.
If your toolchain started failing after upgrading to Wasmtime 20, the fix is not to fight the runtime but to make preopens deterministic: only preopen directories that actually exist, or create them before launching the WebAssembly module.
Understanding the Root Cause
In older Wasmtime releases, a preopen could point at a directory that was missing on disk. The runtime would accept the configuration, and the failure would appear later only if the guest attempted to access that path. Since Wasmtime 20, directory validation happens eagerly while building the WASI context. That means startup now fails as soon as a non-existent host directory is passed as a preopen.
This is generally a safer behavior because it prevents partially invalid sandbox setups. However, some applications depended on the previous behavior without realizing it. A common example is code that assembles a list of candidate directories such as:
- virtual environment package folders,
- tool cache directories,
- platform-specific search paths,
- optional resource folders that may or may not be present.
On Windows, these issues show up more often because installation layouts vary between machines, virtual environments, and packaging tools. When yowasp-yosys or a similar launcher computes a path that does not exist in a specific environment, Wasmtime now refuses to start the module.
Technically, the root issue is simple: the host application is providing invalid preopens. Wasmtime 20 just surfaces that bug earlier and more consistently.
Step-by-Step Solution
The most reliable fix is to filter preopens before they are passed into Wasmtime. If a directory is optional, verify it exists first. If the guest requires it, create it explicitly before startup.
1. Identify where preopens are assembled
Look for code that builds the WASI environment. Depending on the language binding, this may happen in Rust, Python, or a wrapper executable. You are looking for logic equivalent to “preopen this host directory into the guest filesystem”.
2. Filter out missing directories
If the directory is optional, only register it when it exists.
use std::path::PathBuf;
fn existing_dirs(paths: impl IntoIterator<Item = PathBuf>) -> Vec<PathBuf> {
paths
.into_iter()
.filter(|p| p.is_dir())
.collect()
}
let candidates = vec![
PathBuf::from("./share"),
PathBuf::from("./techlibs"),
PathBuf::from("./optional-cache"),
];
let valid_preopens = existing_dirs(candidates);
for dir in valid_preopens {
// Register dir as a WASI preopen here
}
If your code uses the newer builder APIs, apply the same existence check before each call that adds a preopen directory.
3. Create required directories before startup
If the guest expects a directory to exist, create it first instead of assuming lazy failure.
use std::fs;
use std::path::Path;
fn ensure_dir(path: &Path) -> std::io::Result<()> {
if !path.exists() {
fs::create_dir_all(path)?;
}
Ok(())
}
let cache_dir = Path::new("./cache");
ensure_dir(cache_dir)?;
// Now safely preopen cache_dir
4. Apply the same fix in Python launchers
If the issue originates in a Python wrapper around Wasmtime or an application that computes directories dynamically, guard each path before handing it off to the runtime.
from pathlib import Path
candidate_dirs = [
Path("share"),
Path("techlibs"),
Path("optional-cache"),
]
valid_preopens = [p for p in candidate_dirs if p.is_dir()]
for path in valid_preopens:
# add path as a WASI preopen
pass
For required directories:
from pathlib import Path
cache_dir = Path("cache")
cache_dir.mkdir(parents=True, exist_ok=True)
# add cache_dir as a WASI preopen
5. Verify Windows path handling
On Windows, make sure the resolved paths are the actual directories you expect. Virtual environment setups can generate different install locations depending on how packages were installed. Print or log the final paths before preopening them.
from pathlib import Path
for p in candidate_dirs:
print(f"candidate={p} exists={p.exists()} is_dir={p.is_dir()}")
This quickly reveals whether the bug is a missing folder, a wrong virtual environment path, or a file being passed where a directory is required.
6. Short-term workaround: pin Wasmtime below 20
If you need an immediate unblock while waiting for an upstream package fix, temporarily pin to an older Wasmtime version that still accepts missing preopens lazily. This is a workaround, not the long-term solution, because the application still contains invalid preopen configuration.
# Example only: pin the relevant dependency in your environment
# Use the package manager appropriate for your project and dependency chain
The durable fix is to stop passing non-existent directories.
7. Test with a clean virtual environment
Because this issue often appears in Python packaging scenarios, validate the fix in a fresh environment:
python -m venv .venv
.venv\Scripts\activate
pip install yowasp-yosys
Then rerun the failing command after adding path filtering or directory creation in the launcher code.
Common Edge Cases
- Path exists but is a file: Wasmtime expects a directory preopen. Use is_dir(), not just exists().
- Relative paths resolve differently: Running from a different working directory can make a valid path appear missing. Normalize with absolute paths when debugging.
- Virtual environment layout differences: Windows, system Python, and build isolation tools may place resources in different locations.
- Optional plugin directories: Code often includes “maybe present” folders for extensions. These now must be filtered out before preopening.
- Permissions issues: A directory may exist but still be inaccessible. Eager validation can expose permission problems earlier too.
- Cross-version behavior mismatch: Tests written against pre-Wasmtime-20 behavior may incorrectly expect startup to succeed with invalid preopens.
FAQ
Why did this start failing only after upgrading to Wasmtime 20?
Because Wasmtime 20 validates preopened directories eagerly during WASI setup. Earlier versions deferred the failure until the guest first accessed the path.
Is this a Wasmtime bug or an application bug?
In most cases, it is an application bug revealed by stricter runtime behavior. Passing a non-existent directory as a preopen is invalid configuration; Wasmtime 20 simply reports it sooner.
What is the safest fix for packages like yowasp-yosys?
The safest fix is to filter optional directories and create required ones before constructing the WASI context. That keeps the sandbox configuration valid across Wasmtime versions and operating systems.
If you maintain the affected package, update the launcher so that every candidate preopen is validated before being registered. If you are only a consumer, use a temporary version pin while tracking the upstream fix, but treat that as a stopgap rather than a real resolution.