Skip to content

Error codes

Every error mvmforge emits to its structured output carries a stable code string. SDK tests can pattern-match on these codes; the strings are append-only within an IR major version per ADR-0004.

The canonical registry is schema/error-codes.json in the repo. CI cross-checks the registry against the ErrorCode enum in mvmforge-ir.

Format

Errors appear in the canonical-JSON envelope validate (and compile) emit:

{
"errors": [
{
"code": "E_SECRETS_NOT_IMPLEMENTED",
"path": ".apps[0].env.TOKEN",
"detail": "SecretRef values are not supported until the secrets subsystem ADR lands"
}
],
"ok": false
}
  • code is stable. Match on this in tests.
  • path is a JSON-Pointer-ish dotted path identifying where the error is. May be empty for whole-document errors.
  • detail is human-readable. The text is not stable; do not match on it.

v0.1 codes

Schema-version errors

Path: .schema_version

CodeTriggered when
E_UNSUPPORTED_MAJORIR MAJOR is outside the host’s supported set.
E_MINOR_TOO_HIGHIR MINOR exceeds the host’s known minor for the supported MAJOR.
E_MALFORMED_VERSIONschema_version isn’t a valid MAJOR.MINOR string.

Manifest-shape errors

CodeTriggered when
E_UNKNOWN_FIELDThe manifest has a field not declared in the schema (closed-world).
E_MALFORMED_MANIFESTThe file isn’t readable, isn’t valid JSON, or doesn’t deserialize as Workload.

App-list errors

Path: .apps

CodeTriggered when
E_EMPTY_APPSapps is empty. v0 requires at least one app.
E_MULTI_APP_DEFERREDapps has more than one entry. v0 supports exactly one app per workload.

Per-app errors

Paths: .apps[i].source.kind, .apps[i].env.<KEY>, .apps[i].entrypoint.env.<KEY>, .apps[i].network.ports, .volumes[i].persist

CodeTriggered when
E_SOURCE_KIND_DEFERREDapp.source.kind is nix_derivation or oci_image. v0 implements only local_path.
E_SECRETS_NOT_IMPLEMENTEDAn EnvValue is secret_ref. The secrets subsystem awaits a dedicated ADR.
E_NETWORK_PORTS_WITH_NONEapp.network.mode is "none" but ports is non-empty.
E_PERSIST_DEFERREDA Volume.persist is true. v0 doesn’t support persistent volumes.

Compile-time errors

CodeTriggered when
E_COMPILE_OUTPUT_EXISTS_NOT_DIRThe --out path supplied to mvmforge compile exists but is not a directory.
E_COMPILE_STAGING_FAILEDAtomic staging failed (cross-filesystem rename, permission, or I/O error).

Source bundling errors

Per ADR-0008.

CodeTriggered when
E_SOURCE_PATH_NOT_FOUNDapp.source.path does not exist on the compile host.
E_SOURCE_PATH_NOT_DIRapp.source.path exists but is not a directory.
E_SOURCE_COPY_FAILEDI/O failure copying source files into staging (permission, disk full, etc.).
E_SOURCE_GLOB_INVALIDAn entry in include or exclude is not a valid glob.
E_SOURCE_OUT_OF_TREE_SYMLINKA symlink in the source tree resolves to a target outside app.source.path.

Substrate errors

CodeTriggered when
E_MVM_NOT_FOUNDThe mvmctl binary could not be located via MVMFORGE_MVM_BIN or PATH.

Versioning

Per ADR-0004:

  • Append-only within an IR major version. New codes can be added; existing codes can’t be renamed or removed without a superseding ADR and an IR MAJOR bump.
  • String stability. The exact error-code string is the contract surface.
  • Format stability. The enclosing JSON envelope shape ({"errors": [...], "ok": bool}) and the per-error fields (code, path, detail) are stable.

Programmatic consumption

Python

import json, subprocess
result = subprocess.run(
["mvmforge", "validate", "manifest.json"],
capture_output=True, text=True,
)
body = json.loads(result.stdout)
if not body["ok"]:
for err in body["errors"]:
if err["code"] == "E_SECRETS_NOT_IMPLEMENTED":
print(f"Secrets aren't supported yet at {err['path']}")

TypeScript

import { execFileSync } from "node:child_process";
// Use execFileSync (no shell) with argv array — never execSync with a shell string.
const out = execFileSync("mvmforge", ["validate", "manifest.json"], {
encoding: "utf8",
});
const body = JSON.parse(out);
for (const err of body.errors) {
if (err.code === "E_SOURCE_PATH_NOT_FOUND") {
console.error("Source path missing:", err.detail);
}
}

Use execFileSync (or spawnSync with shell: false) for any programmatic invocation. execSync with a shell-string allows injection if any user-controlled value reaches the command line.