How to Fix: Problems with file system calls from tinygo based WASM component
TinyGo WASM file system calls failing with errno 76 usually means your component has no filesystem capability wired in at runtime—not that the Go code is wrong.
When a TinyGo-based WebAssembly component tries to open, read, create, or stat files and every operation returns errno 76: capabilities insufficient, the root problem is almost always the same: the component is executing under a WASI capability-based security model, and the host did not grant access to the directory or file descriptor being used.
Table of Contents
Understanding the Root Cause
This error is fundamentally about WASI permissions. In a native program, filesystem access often feels implicit: if the OS user can access a file, the process can usually try to open it. In a WASM component, especially one running with WASI or a component runtime that follows capability-based access rules, filesystem access is not automatic.
Instead, the host must explicitly provide access to specific directories, files, or preopened resources. If your TinyGo component calls functions like os.Open, os.ReadFile, os.Stat, or os.Create, those calls eventually depend on WASI host functions. If the runtime has not preopened the target directory or mapped the path into the component, the host rejects the operation with capabilities insufficient.
That is why the error appears consistently across different filesystem calls. The failure is usually not the individual API; it is the missing host-side permission boundary.
There are three important layers to understand:
- TinyGo compilation target: your module or component must be compiled for the correct WASI-compatible target.
- WASM execution model: components do not automatically inherit your local machine filesystem.
- Runtime configuration: the host must explicitly grant directory access, often through a preopen or directory mapping.
If any one of those layers is misaligned, filesystem calls fail even though the Go code looks perfectly valid.
Step-by-Step Solution
The fix is to align the component, the runtime, and the filesystem capability configuration.
1. Confirm the Go code is using a supported filesystem path
Start with a minimal TinyGo example that reads from a path you expect to be available inside the WASM runtime.
package main
import (
"fmt"
"os"
)
func main() {
data, err := os.ReadFile("/data/example.txt")
if err != nil {
fmt.Println("read failed:", err)
return
}
fmt.Println(string(data))
}
This code is fine, but /data/example.txt must exist inside the runtime’s exposed filesystem view, not just on your host machine.
2. Compile the TinyGo binary for WASI/component usage
Use the target expected by your runtime. For a standard WASI binary, the command commonly looks like this:
tinygo build -target=wasi -o app.wasm main.go
If you are producing a WASM component rather than a plain core WASM module, your build pipeline may include an adapter or component conversion step depending on the runtime and toolchain you are using.
The key point is that the module format must match the runtime. A mismatch here can lead to misleading behavior, including host call failures that look like filesystem problems.
3. Preopen or map the directory at runtime
This is the step that fixes most errno 76 cases.
If you are using a WASI runtime such as Wasmtime, provide a directory mapping when launching the module. For example:
wasmtime run --dir . app.wasm
That grants the module access to the current host directory. If your code reads ./example.txt, it can now succeed.
If your code expects a specific in-guest path such as /data/example.txt, map the host directory accordingly when your runtime supports guest path mapping. A typical pattern is conceptually like this:
host directory ./fixtures -> guest directory /data
Then your Go code can safely reference:
data, err := os.ReadFile("/data/example.txt")
If you are embedding the runtime programmatically, make sure you configure WASI preopened directories in the host setup code instead of assuming the guest can access arbitrary host paths.
4. Avoid assuming absolute host paths are visible
This will usually fail inside a constrained WASI environment:
os.ReadFile("/Users/alice/project/example.txt")
Even if that file exists on your laptop, the WASM guest typically cannot see it unless the runtime maps that location into the guest namespace.
Prefer paths that are explicitly granted by the host:
os.ReadFile("./example.txt")
os.ReadFile("/data/example.txt")
Both are valid only if the runtime preopens the corresponding directory.
5. Verify whether your runtime supports filesystem access for components
Some runtimes or component host integrations expose different levels of WASI preview support. A core WASI module may support filesystem access one way, while a component model setup may require additional host plumbing.
Check these runtime-specific questions:
- Does the runtime support filesystem APIs for the module/component type you are running?
- Are you using WASI Preview 1, Preview 2, or a component adapter layer?
- Does your host embedder explicitly pass preopened directories?
- Is the filesystem interface available in the component world you generated?
If the host does not expose filesystem capabilities, no TinyGo code change can fix the issue on its own.
6. Test with a minimal read/write example
Once directory access is configured, validate both reading and writing.
package main
import (
"fmt"
"os"
)
func main() {
if err := os.WriteFile("/data/output.txt", []byte("hello from tinygo"), 0644); err != nil {
fmt.Println("write failed:", err)
return
}
b, err := os.ReadFile("/data/output.txt")
if err != nil {
fmt.Println("read failed:", err)
return
}
fmt.Println("ok:", string(b))
}
If writing still fails while reading works, the runtime may have granted read-only capability instead of read-write access.
7. If using a custom host, explicitly configure WASI
When running the component through your own host application, the host must construct the WASI context with filesystem permissions. The exact API depends on the runtime, but the setup usually includes:
- creating a WASI context
- adding one or more preopened directories
- binding that context to the store or component instance
If you skip the preopen step, all filesystem calls will predictably return capabilities insufficient.
Common Edge Cases
Using the wrong path inside the guest
You may preopen a host directory correctly and still fail because the guest path is wrong. For example, the runtime may expose the directory as . while your code expects /data.
Read access granted, write access denied
Some runtime setups intentionally mount directories as read-only. In that case, os.ReadFile works but os.Create or os.WriteFile fails.
Component model versus core WASM differences
A setup that works for a plain WASI module may not work identically for a WASM component. The host integration for components can require different bindings or adapters.
Runtime does not expose the filesystem API you expect
If your environment only supports a subset of WASI interfaces, TinyGo’s os package may compile, but runtime behavior will still fail.
Assuming local development paths exist in production
Even after fixing local tests, deployment often breaks when the runtime in CI, a serverless worker, or a container does not mount the same directories.
Stat, open, and create all failing with the same error
This usually confirms the issue is capability configuration, not file existence. A missing file often produces a different error; errno 76 strongly points to permission or capability scope.
FAQ
Why does TinyGo compile successfully if filesystem calls are not allowed?
Compilation only verifies that the APIs exist and the target supports generating the required imports. The actual permission to access files is enforced at runtime by the WASI host.
Is errno 76 the same as a normal Unix permission denied error?
Not exactly. It is more specific to the capability-based runtime model. The guest is being told it lacks the authority to access that path or descriptor, even if the host OS user technically could.
Can I fix this purely in Go code?
Usually no. You can improve path handling in Go, but if the host does not grant a preopened directory or filesystem capability, the guest code cannot bypass that restriction.
The practical fix is simple: compile for the correct WASI target, run under a runtime that supports filesystem access, and explicitly preopen or map the directories your TinyGo component needs. Once that capability is present, standard Go filesystem calls typically work as expected.