Skip to content

Troubleshooting

github-actions[bot] edited this page Aug 6, 2026 · 2 revisions

Gearbox — every way a mod can be refused, and what to do

The loader never fails silently. Every rejection produces one specific message, shown in the mod menu under the mod's name and printed to the log. This page lists all of them.

If you are debugging, run the validator first — it produces the same diagnostics as the game, on your terminal, without launching anything:

odmod-check mymod.odmod

Add --revoke UI (or any module) to rehearse a user turning a capability off, and --no-run to check the archive without instantiating.


1. The file is not a readable archive

Message Cause Fix
cannot open <path> The file is missing or unreadable. Check the path and permissions.
<path> is empty Zero bytes. Your build script produced nothing; check it for a silent failure.
<path> is not a readable ZIP archive Not a ZIP, or truncated. .odmod is a ZIP. Repack. A partial download does this too.
corrupt central directory at entry N The ZIP index is damaged. Repack from source.
MANIFEST.json failed to decompress (corrupt or bad CRC) The entry's CRC does not match. Repack. If it recurs, your packer is writing bad CRCs.
mod.wasm failed to decompress (corrupt or bad CRC) As above. As above.

2. Archive limits

These are checked against the ZIP index before anything is decompressed, so a bomb costs nothing. See modding.md for the full table.

Message Cause Fix
archive has N entries, over the limit of 4096 Too many files. Bundle assets — one archive per directory of small files, or fewer, larger files.
archive expands to more than 256 MiB Total decompressed size too large. Ship less, or ship assets outside the mod.
entry "X" expands N:1, over the 200:1 per-entry limit One file compresses suspiciously well. Almost always a zip bomb; if it is genuinely a huge run of zeros, store it compressed differently or generate it at runtime.
archive expands N:1 overall, over the 100:1 limit The whole archive compresses too well. Same. Note this can trip even when every individual entry passes.
entry "X" nests deeper than 16 directories Path too deep. Flatten your data/ layout.

3. Unsafe entry names

Rejected outright, because these are how archives escape their extraction directory. The message always names the entry and the reason.

Reason fragment Meaning
absolute path Entry name starts with /.
drive-letter path Entry name looks like C:/….
'..' path component Traversal.
'.' path component Redundant, and a normalisation trick.
backslash in path Windows separator. Use /.
control character in name Bytes below 0x20, or 0x7F.
name is not valid UTF-8 Includes overlong encodings — two spellings of one character is how a traversal slips past a byte comparison.

Most packers will not produce these. If you hit one, whatever generated the archive is doing something unusual — check it rather than working around it.

4. Archive structure

Message Cause Fix
archive has no MANIFEST.json Missing. Every mod needs one, at the archive root.
MANIFEST.json must be the first entry in the archive…found "X" first Wrong order. Add the manifest first. tools/pack_odmod.sh does this; with the zip CLI, name it first. Directory entries before it are fine.
MANIFEST.json is N bytes, over the 262144 byte limit Enormous manifest. Something is wrong; the manifest is metadata.
archive has no mod.wasm Missing module. The module must be named exactly mod.wasm at the archive root.
mod.wasm is N MiB, over the 64 MiB limit Module too large. If you are shipping a language runtime (Tier 2), strip and optimise it.
mod.wasm is not a WebAssembly binary (bad magic…) Not wasm. You packed the wrong file — a native .o, a .wat, or an empty file.

5. Manifest contents

All of these are prefixed MANIFEST.json:.

Message Fix
is not a JSON object Invalid JSON, or the top level is an array. Trailing commas are the usual culprit.
missing integer field "schema" Add "schema": 1.
unsupported schema N This build understands schema 1.
missing field "id" / "name" / "version" / "gearbox" Add it.
"id" must be lowercase reverse-DNS using [a-z0-9._-] and contain a dot Lowercase only. The id is your Storage namespace and trust-pinning key; on a case-insensitive filesystem com.you.Mod and com.you.mod would collide as two identities sharing one store.
"version" must be semver MAJOR.MINOR.PATCH 1.0 is not enough; write 1.0.0.
"name" is longer than 96 bytes Shorten it.
"gearbox" must be MAJOR.MINOR Write "1.1", not "1" or "1.1.0".
targets Gearbox vX.Y, this build provides v1.1 — different major versions are not compatible A major mismatch is refused outright; there is no partial compatibility across majors. Retarget the mod.
(warning, not an error) targets Gearbox v1.2 but this build provides v1.1; newer APIs will be missing You declared a newer minor than the host has. The mod still loads. If it actually imports something from that minor, instantiation fails and names the symbol — see §7. Declaring the oldest minor you actually need is the safer habit.
missing array field "modules" Even a mod using only Core should list ["Core"].
"modules" must contain only strings No nested objects.
dependency is missing "id" / dependency id "X" is not a valid mod id Dependencies are parsed and validated but not yet resolved.
"publicKey" must be prefixed "ed25519:" Or omit it. Signatures are not verified yet.

Requests an unknown module produces its own message listing every module the host knows:

MANIFEST.json requests unknown module "Filesystem". Known modules: Core, GameState.Read, …

This is a hard error by design. Silently dropping a capability you believe you have is worse than refusing to load.

Wrong API major version:

targets Gearbox v2.0, this build provides v1.0 — different major versions are not compatible

A newer minor is not an error — it loads with a warning, and anything from the newer minor is simply absent.

Limits are clamped, not rejected. Asking for more memory or fuel than the host allows produces a warning (requested N memory pages, clamped to 1024), not a failure. Ceilings: 1024 pages (64 MiB) and 100,000,000 fuel/turn.

6. Loading and linking

Now the archive is fine and the module is being instantiated.

Message Cause Fix
mod.wasm did not load: <engine message> Malformed or uses a wasm feature this build does not enable. SIMD, threads, exceptions and GC proposals are off. Bulk memory and reference types are on. Build for plain wasm32-freestanding.
mod.wasm imports "X"."Y", which this host does not provide. You imported something outside the ABI. Almost always your toolchain adding env.abort, wasi_snapshot_preview1.*, or similar. Use freestanding / no-std flags. See §7.
mod.wasm imports "X"."Y" but the <Module> module is not granted You declared the capability but the user revoked it in Advanced, or you never declared it. Declare it in modules, and handle being refused — a user is allowed to say no.
mod.wasm imports a non-function ("X"."Y") You tried to import a memory, table or global. Only host functions may be imported. Define your own memory.
mod.wasm did not instantiate: <engine message> Usually a start function trapping, or a limits conflict. Avoid a wasm start section; do your work in mod_load.
could not create an execution environment for the mod Host resource failure. Rare; report it.
duplicate mod id, already provided by <file> Two .odmod files in the mods directory declare the same id. Delete one. The id is the identity, not the filename.

7. "This host does not provide that import" — the common causes

This is the single most likely failure for a language other than C, and it is always the same shape: your toolchain emitted an import you did not ask for.

Import you see Toolchain Fix
env.abort AssemblyScript Compile with --use abort=.
env.trace, env.seed AssemblyScript Avoid trace() and Math.random().
wasi_snapshot_preview1.* TinyGo, Rust with wasm32-wasi, Zig with wasi Target wasm-unknown (TinyGo), wasm32-unknown-unknown (Rust), wasm32-freestanding (Zig).
env.memory Anything linking with --import-memory Drop that flag; define your own memory.
env.__linear_memory, env.__stack_pointer Object file, not linked You packed a .o. Run the linker.
go.* Standard Go (GOOS=js) Standard Go cannot target this host; use TinyGo.

Inspect what your module actually imports before guessing:

python3 tools/wasm_imports.py mymod.odmod

It accepts a .wasm or a .odmod, flags any import outside the ABI, checks that mod_load is exported, and exits non-zero if either is wrong — so it drops straight into a build script.

8. Runtime failures, after a successful load

These disable the mod mid-session and show the message in the mod menu.

Message Cause Fix
mod_load refused the load with code N Your mod_load returned non-zero. That is your own code declining. Return 0 to accept.
mod does not export mod_load Export missing or misnamed. The name must be exactly mod_load. Check your export attribute actually took effect — inspect the export section.
mod_pre_turn: Exception: out of bounds memory access You handed the host, or yourself, a bad pointer. Bounds-check your own indices; the host refuses out-of-range (ptr,len) but cannot fix your arithmetic.
…: Exception: unreachable A trap: failed assertion, panic, integer divide by zero, or a language runtime aborting. In Rust/Zig/AS, a panic compiles to unreachable. Handle the error instead.
…: Exception: wasm operand stack overflow Runaway recursion. The stack is 64 KiB.
Mod stops mid-turn with no further logging Fuel exhausted. You exceeded limits.fuelPerTurn. Raise it in the manifest (capped at 100,000,000) or do less work per turn. Not enforced on web — see below.
WASM module load failed: fast interpreter offset overflow The module is too big for WAMR's fast interpreter. A function exceeds the INT16_MAX operand-stack or local-count limit. Only CPython does this in practice. Build the host with -DOD_MODS_FAST_INTERP=OFF; see Languages.
the mod called exit(71) wasi-libc gave up during startup. 71 is EX_OSERR. wasi-libc calls _Exit(EX_OSERR) when its preopen scan gets anything but EBADF from fd_prestat_get. If you see this from your own runtime, it died before your code ran.
mod_load: Exception: instruction limit exceeded Load ran out of fuel. mod_load is metered against limits.loadFuel, not fuelPerTurn. It defaults to 500,000,000, so hitting this means either you set loadFuel yourself or your interpreter really is that expensive to start.
aborted: <your message> You called gearbox_abort. Working as intended.

9. Things that are not errors

Worth knowing so you do not chase them.

  • Cannot override max memory with value greater than module max memory in the log. This comes from the engine and is misleading: your manifest's memoryPages is the cap that applies. Verified by tests/mod_runtime_test.cpp, which grows a module to exactly the manifest limit and no further.
  • Needs reload to take effect is a state, not a failure. You enabled a mod while a game was running, or changed its permissions. Reload the modloader.
  • ignoring unrecognised entry "X" is a warning. Only MANIFEST.json, mod.wasm, thumbnail.png, signature.bin and data/** are used; anything else is carried but ignored.
  • A thumbnail warning never fails a load. A non-PNG or one over 512×512 is dropped with a note.
  • archive contains N data/ file(s) but does not request the Assets module — your files are there but unreadable by you. Add "Assets" to modules.

10. Platform differences that look like bugs

Symptom Explanation
Panels do nothing, panel_register returns 0 is_headless is 1. Self-play training runs thousands of turns with no renderer. Check gearbox_env and skip UI work.
An infinite loop hangs the browser tab Fuel is not enforced on web. The browser exposes no instruction limit. On desktop the interpreter terminates you. Bound your own loops.
Mods vanish after a page refresh on web Deliberate — web mods are session-scoped. Re-add them; loading is hot, so it costs one interaction.
The mod menu says the runtime is unavailable The game was built with -DOD_ENABLE_MODS=OFF, or WAMR could not be fetched at configure time. The game still runs; mods cannot.
You cannot turn on AI Learning A mod is enabled. The two are mutually exclusive: a mod can change what the AI observes, and training against that corrupts data/ai/model.bin invisibly. Disable your mods first.