Troubleshooting
This page is organized by failure mode. Find the shape that matches your problem, read the cause, apply the fix. Most failures pair a process exit code with a machine-friendly errors[0].code; route on the exit code first, then on the code. The full catalog is in Understanding Results -> errors[].code catalog.
Examples use the canonical calc project (a small Python package) and Nove Test 0.1.2.
In TEXT mode (the default on a terminal), a failed command renders a generic error block, regardless of which verb produced it:
✗ <command>
<code>: <human-readable message>The first line names the failing command; the second gives the error code (useful for searching docs / issues) and a one-sentence description. For full detail, re-run with novetest --output json.
Every envelope has exactly these top-level keys (JSON-sorted): command, data, errors, ok, schema, warnings. schema is always "novetest/v1"; errors/warnings are arrays of {code, message, details}. There is no top-level version, verb, or exit_code field. Route on the process exit code, then on errors[0].code:
err = envelope["errors"][0] if envelope["errors"] else None
code = err and err["code"]
# install hints for engine errors live in data.engine_readiness.issues[],
# NOT in err["details"].Exit codes at a glance
| Exit | Meaning |
|---|---|
| 0 | Success (ok: true). |
| 1 | Generic / unexpected failure (e.g. cli-error). |
| 2 | Usage / validation: uninitialized store, bad argument, malformed test target, unknown run_id, bad flag, reset without --confirm. |
| 3 | Your tests failed or errored. The tool worked (ok: true); failing tests — and a suite that errored before producing results — are data, not an error. |
| 4 | The engine could not run: no/insufficient engine, or an adapter invocation error. |
| 5 | Project Store storage error (corrupt store, wipe failed). |
Exit 3 is not a tooling error. A failing — or errored — test run still reports ok: true. Treat it like a failing pytest invocation — read the recommendation block, fix the code or the test, re-run.
Quick reference — exit code × errors[0].code
| Exit | errors[0].code | Recovery |
|---|---|---|
| 0 | (none) | Success. |
| 1 | cli-error | Uncaught internal error (command: "cli"). Capture, file issue. Do not retry blindly. |
| 2 | uninitialized | novetest init, then retry. |
| 2 | not-found | Unknown run_id. novetest memory list, pick a real ULID. |
| 2 | invalid-flag | Value outside the closed set. Read errors[0].message. |
| 2 | confirm-required | reset needs --confirm (destructive). |
| 2 | adapter-invalid-target | Malformed test target rejected pre-spawn (dash-/flag-leading). Fix the target argument, not the engine. |
| 2 | engine-ambiguous | ≥ 2 viable engines at the init directory — nothing created. Re-issue novetest init --engine <name> with a value from data.candidates[]. |
| 3 | (none) | ok: true — user's tests failed or errored. Read data.recommendations (or run_record.status on run). |
| 4 | engine-missing | No usable engine. Read data.engine_readiness.issues[]. |
| 4 | engine-misconfigured | Engine applies, tooling missing. Read data.engine_readiness.issues[]. |
| 4 | no-engine-detected | No supported engine marker — init created nothing (data.candidates[] lists discovered sub-projects), or run/test hit a marker-less anchor. cd into a real project and novetest init (or --engine <name>). |
| 4 | adapter-<kind> | Engine ran but failed (build error, missing plugin, timeout). Read errors[0].message (stderr tail). |
| 5 | store-corrupt / store-wipe-failed | Storage failure. Surface to operator. |
Two things that bite parsers: an unready engine surfaces the readiness state verbatim as the error code (engine-missing / engine-misconfigured — the code IS the state, no extra prefix); adapter codes key on the failure kind (adapter-unparseable-output, adapter-invalid-target), not the engine name. There is no engine-not-ready code and no not-implemented runtime code.
Install issues
command not found: novetest
The install script drops the binary at ~/.local/bin/novetest (POSIX) or %USERPROFILE%\.local\bin\novetest.exe (Windows), which may not be on PATH.
Fix. Add it:
# Linux / macOS — add to ~/.bashrc or ~/.zshrc
export PATH="$HOME/.local/bin:$PATH"# Windows — add to PowerShell profile
$env:PATH = "$HOME\.local\bin;$env:PATH"Re-check with novetest --version (novetest 0.1.2 (Python …)).
Linux: version 'GLIBC_2.xx' not found
The install succeeded — SHA-256 verified, binary written — but every novetest invocation dies instantly, before any output, with a dynamic-loader error:
novetest: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.39' not found
(required by /home/you/.local/bin/novetest)(The version number in the error names whichever glibc symbol your installed binary needs — you may see a different number than 2.39; see the table below.)
Cause. The Linux binaries of every release up to and including 0.2.1 are PyApp bundles built against glibc 2.39, so they need a host glibc >= 2.39. From v0.3.0, that floor is much lower — glibc >= 2.31 (a CI-enforced ceiling; the v0.3.0 binary as shipped in fact requires only GLIBC_2.30). Downgrading never helps on either side of that line: every release before v0.3.0 carries the 2.39 floor or higher. Nothing is wrong with your download either: the failure happens in the loader, before the process starts, so it costs milliseconds and produces no novetest/v1 envelope at all. Each release states its own minimum in its release notes; check those for the version you are installing.
Check your host's glibc:
ldd --version | head -1 # e.g. "ldd (Debian GLIBC 2.36-9+deb12u14) 2.36"Then compare against the two floors:
| Host | glibc | Up to & incl. 0.2.1 | v0.3.0+ |
|---|---|---|---|
Debian 12 "bookworm" — the base of the official node images | 2.36 | no | yes |
| Debian 11 "bullseye" | 2.31 | no | yes |
| Ubuntu 22.04 LTS | 2.35 | no | yes |
| Ubuntu 20.04 LTS | 2.31 | no | yes |
| RHEL / Rocky / Alma 9 | 2.34 | no | yes |
| Amazon Linux 2023 | 2.34 | no | yes |
| Fedora 39 | 2.38 | no | yes |
| Ubuntu 24.04 LTS / Debian 13 "trixie" / Fedora 40+ | >= 2.39 | yes | yes |
| Debian 10, RHEL/CentOS 8, Ubuntu 18.04, Amazon Linux 2 | <= 2.28 | no | no — below both floors |
Fix — upgrade to the current release first. If you installed before v0.3.0, re-running the install one-liner overwrites the binary in place and very likely fixes this on its own — most hosts the old 2.39 floor excluded are inside the new 2.31 one:
curl -fsSL https://ailovestesting.com/products/novetest/install.sh | sh
novetest --versionStill below the floor? Install from PyPI instead. The Python-tooling install (see Installation -> Other ways to install) runs novetest on your host's own Python instead of the CPython the binary bundles, so no binary glibc floor applies to it:
uv tool install novetest # or: pipx install novetest
novetest --version # expect ok: true, schema "novetest/v1"It needs Python >= 3.11 on the host. Everything after installation — verbs, envelopes, exit codes, the Project Store — is identical either way.
Otherwise, move to a host or base image with glibc >= 2.31 (or >= 2.39 if you must stay on a release before v0.3.0).
Agents: recognize this by the fragment GLIBC_2. together with not found on a libc.so.6 line, on a novetest call that exits 1 in a few milliseconds. On a currently-blocked host, first try upgrading (re-run the install one-liner — v0.3.0+ needs only glibc >= 2.31); if that still fails, switch to the PyPI install. Do not retry the binary as-is, and do not fall back to an older release: every release before v0.3.0 carries the 2.39 floor or higher.
Install script aborts with SHA-256 mismatch
The install script downloads the binary plus its .sha256 sidecar, verifies the hash, and aborts loudly, writing nothing, on a mismatch — a real integrity guard. The download was corrupted or the release artifacts are inconsistent.
Fix.
- Re-run the install script (usually a flaky network).
- Pin a version:
NOVETEST_INSTALL_VERSION=v0.1.2 curl … | sh. - Still failing? File an issue with your OS / arch / network situation.
First run is slow (5–15 seconds)
Expected. The binary is PyApp-wrapped — it unpacks its bundled CPython once, on the first invocation per binary version. Later runs are warm.
Windows: PowerShell script blocked
Your execution policy is blocking install.ps1.
Fix.
powershell -ExecutionPolicy Bypass -File install.ps1
# or
Unblock-File install.ps1; .\install.ps1Supported platforms
Linux x86_64, Linux aarch64, macOS universal2 (one fat binary for Intel + Apple Silicon), Windows x86_64. No Windows arm64 build.
Linux hosts additionally need glibc >= 2.39 to run the binaries of releases up to and including 0.2.1 — check with ldd --version, and check each release's notes for its own minimum. From v0.3.0 that floor drops to glibc >= 2.31. Below the floor, use the PyPI install path; see the GLIBC_2.xx entry above.
init issues
store-corrupt (exit 5)
.novetest/store.json exists but is unreadable or malformed:
✗ init
store-corrupt: Project Store at /home/you/calc-demo/.novetest is unreadable: <reason>.Fix. Salvage the file if you can; otherwise:
rm -rf .novetest
novetest initThat wipes stored runs for this project. Source and tests are untouched.
{
"errors": [
{ "code": "store-corrupt", "details": {}, "message": "Project Store at <path> is unreadable: <reason>." }
]
}Do NOT auto-recover — destructive recovery destroys history. Surface to the operator.
init succeeded but engine readiness: engine-missing
When init recognizes your ecosystem (project markers exist) but can't find a usable engine, it still succeeds and leads with ✓ — the readiness line reports what's missing. (A directory with no recognized markers at all, or with two or more viable engines, is different: init refuses and creates nothing — see the next section.)
✓ Initialized .novetest/ at /path/to/calc-demo/.novetest
engine readiness: engine-missing — no engine detected
issue: Python workspace detected but no pytest configuration (pytest.ini, [tool.pytest.ini_options], conftest.py, or tests/ dir) found(This issue: is for a Python project with a pyproject.toml but no pytest config — matching the agent-tab envelope below. A directory with no recognized markers at all doesn't get this far — init refuses with no-engine-detected and creates nothing; see the next section. A healthy project shows engine readiness: ready — python/pytest 9.0.3.)
The issue: line carries the real, engine-specific reason and install command. Common cases:
| Engine | Fix |
|---|---|
| pytest | Add a tests/ dir / [tool.pytest.ini_options] / pytest.ini / conftest.py; install pytest + pytest-json-report. |
| jest | Install Node.js ≥ 18, then npm install --save-dev jest in the project root. |
| go-test | Install Go ≥ 1.21 (https://go.dev/dl/). |
| cargo-test | cargo install cargo-nextest --locked (nextest is required — no plain cargo test fallback). |
| junit | Install JDK 17+ and Maven 3.9+ or Gradle 7.6+. JUnit 5 Jupiter only. |
| xunit | Install .NET SDK 8.0+ and add xunit (v2). MSTest / NUnit are rejected. |
Re-run novetest init after fixing.
{
"command": "init",
"data": {
"engine_readiness": {
"ecosystem": null, "engine": null, "engine_version": null,
"evidence": ["pyproject.toml"],
"issues": ["Python workspace detected but no pytest configuration (pytest.ini, [tool.pytest.ini_options], conftest.py, or tests/ dir) found"],
"state": "engine-missing"
}
},
"errors": [],
"ok": true,
"schema": "novetest/v1",
"warnings": []
}When init gets as far as creating a store, it is ok: true, exit 0 — route off data.engine_readiness.state, one of exactly ready, engine-missing, engine-misconfigured (there is no engine-not-ready state). The actionable, engine-specific install commands are the strings in data.engine_readiness.issues[].
no-engine-detected (exit 4) / engine-ambiguous (exit 2) — init refused, nothing created
Two cases where init does not create a store. (Per-engine detection markers and the selection rules live in Supported Languages.)
no-engine-detected (exit 4). No supported engine marker at this directory:
✗ init
no-engine-detected: No supported engine marker found at /path/to/dir; no Project Store was created. Note: candidate projects were discovered below (data.candidates); cd into the one you want and run `novetest init` there — novetest never initializes a directory you are not standing in.Fix. cd into your actual project (or one of the listed candidate sub-projects) and run novetest init there. novetest deliberately never initializes a directory you are not standing in, and it refuses to even scan for candidates at a filesystem root or $HOME.
engine-ambiguous (exit 2). Two or more viable engines (marker present AND toolchain ready) at this directory — reported by init, or by an execution verb (run / test / reset) reaching a legacy pin-less store at such a root:
✗ init
engine-ambiguous: Multiple viable engines detected (pytest, jest); no Project Store was created. Choose one explicitly: `novetest init --engine <name>`.Fix. Pick one explicitly:
novetest init --engine pytestRead-only verbs (status, memory list/show/delete, inspect, coverage show, regression compare/latest, localization <run_id>/latest, compare) never refuse this way on a legacy pin-less store — they answer engine-less and write nothing; only the execution path needs an engine.
no-engine-detected (exit 4): the payload is data.candidates[] — {ecosystem, engine_name, path} sub-projects found by a bounded scan (depth ≤ 2) — plus data.scan_refused (true at / and $HOME, where the scan is not attempted). Recovery: cd into a candidate path and run novetest init there. Do NOT create .novetest/ in directories the operator didn't designate.
engine-ambiguous (exit 2): ≥ 2 viable engines at the init directory — or a legacy pin-less store at such a root reached by an execution verb (run / test / init / reset). data.candidates[] lists the viable engines. Recovery: re-issue novetest init --engine <name> with a value from data.candidates[]. Viability is host-dependent (it checks the toolchain, not just markers) — never cache this outcome across machines.
Read-only verbs (status, memory list/show/delete, inspect, coverage show, regression compare/latest, localization <run_id>/latest, compare) do not hit this on a legacy pin-less store: they proceed engine-less (exit 0) and answer from the store as-is, writing nothing. Only the execution path needs an engine, so only it refuses on ambiguity and only it backfills the pin.
One more shape to expect: run/test against a marker-less anchor (a pin-less legacy store with no engine marker at the workspace root) surfaces the same no-engine-detected code (exit 4), but the payload is data.engine_readiness (state: "engine-missing"), not data.candidates. Recovery: novetest init (or novetest init --engine <name>) at the anchor to pin an engine, then retry.
test / run issues
engine-missing (exit 4)
✗ run
engine-missing: engine readiness state: engine-missing (engine=(none detected))No usable engine. The code is the readiness state verbatim — engine-missing (the code IS the state; there is no extra engine- prefix). novetest test behaves the same.
Fix. Run novetest init (or re-run the verb — test/run re-probe each time) and read the readiness issue: lines, then install the engine. If the engine is installed but unseen:
- Wrong interpreter (pytest). Nove Test runs pytest with its own bundled interpreter (
<python> -m pytest …), not apyteston PATH. A pytest it can't import reads asengine-misconfigured. - Wrong project. Confirm you're in the right directory.
{
"command": "run",
"data": {
"engine_readiness": {
"ecosystem": null, "engine": null, "engine_version": null,
"evidence": ["pyproject.toml"],
"issues": ["Python workspace detected but no pytest configuration … found"],
"state": "engine-missing"
}
},
"errors": [
{ "code": "engine-missing", "details": {}, "message": "engine readiness state: engine-missing (engine=(none detected))" }
],
"ok": false,
"schema": "novetest/v1",
"warnings": []
}The install hints are in data.engine_readiness.issues[] — not in errors[0].details (which is {}). Real issues[] examples carry the exact command, e.g. "pytest is not importable from the resolved interpreter (<interpreter>); install pytest, pytest-json-report and pytest-cov into <workspace>/.venv … — from <workspace> run: python3 -m venv .venv && .venv/bin/python -m pip install pytest pytest-json-report pytest-cov" or "cargo nextest is not installed … Install with: cargo install cargo-nextest --locked …". A misconfigured engine surfaces as the code engine-misconfigured (the code IS the state, verbatim). If policy permits installs, execute the hint and retry; otherwise surface it.
adapter-<kind> (exit 4)
The engine launched but failed before producing parseable results — a build error, a missing plugin, or a tool exiting non-zero. The code is adapter-<kind> (e.g. adapter-unparseable-output, adapter-missing-plugin, adapter-missing-binary, adapter-timed-out); the message includes the engine's own stderr tail. (A malformed target argument is different — that is adapter-invalid-target, a usage error at exit 2; see below.)
Fix. Read the stderr tail — the problem is at the engine level (your build, your dependencies), not in Nove Test. Fix it and re-run.
A common pytest case: your project's .venv ships pytest but not pytest-json-report — novetest runs your venv's pytest (interpreter resolution is venv-first) and refuses with adapter-missing-plugin (exit 4), hint: pip install pytest-json-report into your project's venv.
An unknown token like novetest frobnicate is not "command not found": the first non-verb token is treated as a test selector (novetest test frobnicate), so the engine tries to run it and fails with an adapter error.
{
"command": "test",
"data": {},
"errors": [
{ "code": "adapter-unparseable-output", "details": {}, "message": "pytest-cov did not write coverage JSON to …/coverage.json; stderr tail: ERROR: file or directory not found: bogusverb\n\n" }
],
"ok": false,
"schema": "novetest/v1",
"warnings": []
}<kind> ∈ unparseable-output, missing-plugin, missing-binary, missing-engine, timed-out, misconfigured-environment (varies by adapter). Engine-level issue — read errors[0].message, fix, retry. Some adapters attach details.install_hint. (invalid-target is the one exception — a bad target argument, exit 2 not exit 4; see below.)
adapter-invalid-target (exit 2) — usage error
Different from the engine-level adapter-<kind> failures above: the test target you passed was rejected at the adapter boundary before any engine ran — a dash-/flag-leading expression a native engine would swallow as its own flag (e.g. novetest run -- --pdb). Nothing is wrong with your engine or your build; the argument is malformed.
Fix. Correct the target expression — drop or quote the leading dash, or pass a real test path/selector. This is a usage error (exit 2), so do not reach for install/readiness remediation.
errors[0].code == "adapter-invalid-target", exit 2 (usage error) — the target string was rejected pre-spawn (a dash-/flag-/metachar-leading token an engine would consume as a flag). The code string is unchanged from the other adapter kinds' naming, but the exit is 2, not 4: treat it as a bad caller argument (fix the target), not an unready engine. Every other adapter-<kind> stays exit 4.
Exit code 3 (tests failed or errored) — NOT an error
Your tests actually failed — or a suite errored before producing results (run_record.status == "errored") — with ok: true, exit 3. The recommendation block names where to look:
3 recommendations · 2 categories · run_id=…
! [investigate_location] Investigate `subtract`@6 in `calc/arithmetic.py` (rank 1, ochiai=1.000, sbfl_per_test).Fix the code or the test, re-run.
if exit_code == 3:
assert env["ok"] is True # DATA, not a tool error
for rec in env["data"]["recommendations"]:
route(rec["category"]) # 7 categories; priority int, lower = higherRecommendations carry a category and an int priority (1–7, lower = higher) — there is no severity field. Categories: regression_with_localization (1), investigate_location (2), investigate_regression (3), coverage_gap (4), flaky_suspected (5, fires only with novetest test --reruns N, N ≥ 1), unavailable_analysis (6), all_green (7, exclusive).
Explicit target matched nothing (exit 0 — but check the counts)
An explicit target that matches nothing (a typo, a path that isn't anchor-relative) is not an error: the run collects zero tests and reports success — collected: 0, total: 0, status: "passed", exit 0. Before celebrating a green targeted run, make sure it actually ran something (the run header / novetest inspect shows the counts).
Zero-collected explicit targets yield collected: 0, total: 0, status: "passed", exit 0 — and all_green, because everything that ran did pass. The verb-independent signal is the warning: before treating a targeted run as green, assert "zero-tests-collected" is not among warnings[].code (it rides on both test and run). If you want the count itself, the path is verb-dependent: on run it is data.memory_entry.run_record.summary_counts.total; a test envelope carries no memory_entry at all — take data.run_reference.run_id and read the counts back via novetest inspect <run_id> (data.run_summary.summary_counts.total). The sibling shape has its own code: a suite that never ran at all (collection-time syntax error, failing module-scope import) exits 3 with warning suite-did-not-execute and routes to unavailable_analysis, never all_green — "ran, found nothing" vs. "never ran" are separated only by the warning code.
not-found (exit 2) — bad run_id
✗ coverage.show
not-found: No Memory Entry for run_id='FAKE123'A run_id passed to inspect / coverage show / regression compare / localization / replay / memory show / memory delete matched no run.
Fix.
novetest memory listCopy a real ULID (26 chars, e.g. 01KVYRRRN9FWVNQWVHNE1QHAQ4); partial matches are rejected.
{
"command": "coverage.show",
"data": {},
"errors": [
{ "code": "not-found", "details": {}, "message": "No Memory Entry for run_id='FAKE123'" }
],
"ok": false,
"schema": "novetest/v1",
"warnings": []
}novetest memory list, read data.entries[].run_record.run_reference.run_id, retry. Look-up + retry is always safe.
Coverage issues
— unavailable (missing-derived-facts)
✓ per-test · 13/13 statements (100.0%) · run_id=… ← healthy
— unavailable (missing-derived-facts) ← no coverage recordedcoverage show is a cache read — it never derives on demand. If the run came from novetest run without --coverage, no facts were recorded.
Fix.
novetest run --coverage # -c is the short form
# or
novetest test # `test` ALWAYS collects coveragenovetest test has no --coverage flag (it always collects); only novetest run takes --coverage / -c.
coverage show returns exit 0 / ok: true even when facts are missing — unavailability is data:
"coverage_outcome": {
"kind": "unavailable",
"reason": "missing-derived-facts",
"detail": "No coverage_facts.json found for this run; call derive_coverage_facts first",
"run_reference": { … }
}Re-run with novetest run --coverage (or novetest test) to populate. Reason strings are hyphenated across all engines (missing-derived-facts, missing-native-payload, …) — missing-derived-facts is the literal same token wherever the concept appears.
Go projects never produce coverage facts. The go-test adapter runs your tests and writes a coverage profile, but the coverage engine doesn't consume it, so --coverage on a Go project yields an unavailable coverage outcome. The other five engines (pytest, jest, cargo-test, junit, xunit) do produce coverage facts.
localization issues
— unavailable (no-failed-tests)
Expected on a green run — SBFL has nothing to rank. No fix needed. (All engines share one hyphenated reason convention.)
— unavailable (missing-derived-facts)
The run lacks the per-test data SBFL needs (commonly: coverage was unavailable). Make sure coverage is collected (novetest test, or novetest run --coverage), then re-run novetest localization <run_id>.
invalid-flag (exit 2)
✗ localization
invalid-flag: Invalid --formula='nope'; expected one of ['dstar2', 'ochiai', 'op2', 'tarantula']Pick a value from the set: ochiai (default), op2, dstar2 (note: dstar2, not dstar), tarantula. --top-n must be a positive integer (default 10).
⚠ localization-cache-rederived
You re-invoked with a --formula/--top-n that differs from the cached run. The CLI invalidated the cache and re-derived. Informational.
⚠ localization-formula-noop-in-mode
--formula was ignored because the SBFL mode is failure_proximity (which pins ochiai). Drop the flag, or accept the warning.
localization returns exit 0 / ok: true even when unavailable:
"localization_outcome": {
"kind": "unavailable",
"reason": "no-failed-tests",
"detail": "run has no failed test results",
"run_reference": { … }
}Localization reasons are hyphenated, like every other engine's: no-failed-tests, no-coverage, no-run-evidence, missing-derived-facts, run-not-analyzable (missing-derived-facts is the literal same token across coverage / regression / localization / replay). A bad flag is exit 2:
{
"command": "localization",
"data": {},
"errors": [
{ "code": "invalid-flag", "details": {}, "message": "Invalid --formula='nope'; expected one of ['dstar2', 'ochiai', 'op2', 'tarantula']" }
],
"ok": false, "schema": "novetest/v1", "warnings": []
}--formula ∈ {ochiai, op2, dstar2, tarantula}; --top-n ≥ 1 (Invalid --top-n=0; expected a positive integer). Warnings localization-cache-rederived / localization-formula-noop-in-mode never affect ok or exit code.
reset issues
confirm-required (exit 2)
✗ reset
confirm-required: `novetest reset` is destructive. Pass --confirm to acknowledge.reset hard-wipes the store and refuses to run without acknowledgement.
Fix.
novetest reset --confirm✓ Reset .novetest/ at /path/to/.novetest
removed: nothing
engine readiness: ready — python/pytest 9.0.3reset --confirm is the only hard wipe. memory delete <run_id> only tombstones a run — it still appears in memory list/memory show with a tombstoned_at timestamp.
{
"command": "reset",
"data": {},
"errors": [
{ "code": "confirm-required", "details": {}, "message": "`novetest reset` is destructive. Pass --confirm to acknowledge." }
],
"ok": false, "schema": "novetest/v1", "warnings": []
}Re-issue as novetest reset --confirm ONLY with operator approval — it hard-wipes all runs/findings. memory delete tombstones reversibly.
replay issues
replay actually re-executes the run, so it can hit engine problems. A healthy replay reads ✓ reproducible · 1/1 · run_id=….
? unavailable (engine-not-ready) / ? unavailable (target-missing)
The engine binary is gone, or the original target no longer exists (exit 4). Same fix as engine-missing: install/configure the engine, or restore the target.
? unavailable (missing-derived-facts)
Not enough recorded evidence to replay (exit 0 — data, not an error).
Tuning
novetest replay <run_id> --reruns 3 --timeout 1200--reruns defaults to 1, --timeout to 600.0 seconds.
inspect's replay ? unavailable (missing-derived-facts) line is expected — inspect is a pure read and never replays. Use novetest replay <run_id> to actually replay.
replay is the one read-style verb whose unavailable reasons split by exit code: engine-not-ready / target-missing → exit 4; original-not-found → exit 2; tombstoned-original / context-reconstruction-failed / missing-derived-facts → exit 0 (ok: true). --reruns (default 1) and --timeout (default 600.0) control re-execution.
Output-mode issues
"I see JSON everywhere, not pretty text"
TEXT mode is used only when stdout is a real terminal; piped/redirected output defaults to JSON. You're either piping (novetest test | less → use novetest --output text test) or have NOVETEST_OUTPUT=json exported (unset NOVETEST_OUTPUT). Precedence is --output > NOVETEST_OUTPUT > TTY autodetect.
"My CI logs are full of pretty JSON; I want one line per envelope"
NOVETEST_OUTPUT=ndjson novetest testNDJSON is one compact line per envelope.
--output bogus prints a traceback
An invalid --output (or NOVETEST_OUTPUT) value is rejected before the envelope machinery starts — raw traceback, exit 1, no envelope. Use only text, json, or ndjson.
"I expected color"
There is no ANSI color. The glyph palette (✓ ✗ — ⚠ ! ? · ↳) carries meaning instead.
"I get text when I expected JSON"
You're invoking from a TTY without an override. Pin once:
export NOVETEST_OUTPUT=jsonOr per-invocation: novetest --output json <verb>. JSON is pretty-printed (indent 2, sorted keys); NDJSON is one compact line. Precedence: --output > NOVETEST_OUTPUT > TTY autodetect. An invalid value raises before the envelope is built (traceback, exit 1) — validate your value. Never parse text mode; only the JSON/NDJSON shape is stable.
Project Store issues
store-corrupt / store-wipe-failed (exit 5)
Two distinct corruption classes share the store-corrupt code — the message's path tells them apart:
- Store metadata —
.novetest/store.jsonis missing, malformed, or permission-blocked (store-corrupt); or a filesystem error interruptedreset(store-wipe-failed). - A single run record — the
record.jsonof the exact run you addressed byrun_id(memory show/memory delete/coverage show/coverage diff/regression compare/compare/localization/replay/inspect) is torn, hand-mangled, or written by a newer schema. The message names the corrupt file's absolute path. This is NOTnot-found: the run exists, its storage is unreadable — re-running or picking another id will not fix it.
Scan verbs are unaffected by the record class: memory list still exits 0, skips the bad record, and attaches one corrupt-run-record-skipped warning per skip (see Understanding Results, warnings catalog).
Fix. Split by the class:
Message path ends in
store.json(or the code isstore-wipe-failed): check permissions, salvage the file if you can; worst casebashrm -rf .novetest novetest initYou lose ALL run history but recover the store. (Agents: do NOT auto-recover — this destroys data; surface to the operator.)
- Message path ends in
record.json: only that ONE run's evidence is affected — every other run stays intact and listable.memory deletecannot tombstone a corrupt record (tombstoning re-writes the parsed record), so recovery is manual: with operator approval, delete the namedrecord.json'srun_<id>/parent directory, then re-run the tests to produce fresh evidence.
"How do I share run history with my team?"
Commit .novetest/ to git — plain JSON, diffs cleanly. Most teams choose NOT to (history is large and per-developer); do it only for a single team-shared baseline.
"How do I clean up old runs?"
novetest memory delete <run_id> # tombstone one run
# or wipe everything:
novetest reset --confirmIdempotency and retry policy (agent reference)
(Mostly matters for agents and CI. Interactively, retrying any verb is safe; reset --confirm and rm -rf .novetest are the only destructive actions.)
| Verb | Idempotent? | Retry policy |
|---|---|---|
init | Yes (no-op on existing store) | Retry safe. |
test / run | Each call produces a new run | Retry produces a new run_id. |
status / inspect | Read-only | Retry safe. |
coverage show / diff | Read-only | Retry safe. |
regression compare / latest | Cache-aware | Retry safe. |
localization / latest | Cache-aware; warns on rederive | Retry safe; cache invalidated if flags differ. |
compare | Read-only | Retry safe. |
replay | Re-executes | Retry safe; each adds another rerun. |
memory list / show | Read-only | Retry safe. |
memory delete | Tombstone is atomic | Retry safe (re-tombstone is a no-op). |
reset --confirm | Wipes + re-inits | Destructive — operator approval only. |
licenses | Read-only | Retry safe. |
Nove Test does no network I/O at invocation time (only the install script does, once).
When to abort vs when to recover
(Same caveat — this is mostly for agents.)
| Situation | Action |
|---|---|
| Exit 0 / 3 | Success path — read the envelope (3 = tests failed or errored, still ok: true). |
Exit 2, uninitialized | Auto-recover: init then retry. |
Exit 2, not-found | Auto-recover: memory list and pick. |
Exit 2, invalid-flag | Auto-recover: fix the value. |
Exit 2, confirm-required | Re-issue with --confirm ONLY with operator approval. |
Exit 2, adapter-invalid-target | Fix the target argument (drop/quote the leading dash) — a caller usage error, not an engine issue. |
Exit 2, engine-ambiguous | Auto-recover: re-issue novetest init --engine <name> with a value from data.candidates[]. |
Exit 5, store-corrupt / store-wipe-failed | If the message names a record.json: one corrupt run — surface; operator may delete just that run_<id>/ dir and re-run. Otherwise (store.json / wipe): do NOT auto-recover — surface (destructive recovery destroys ALL history). |
Exit 4, engine-missing / engine-misconfigured | Auto-recover IF policy permits installs (use data.engine_readiness.issues[]). Else surface. |
Exit 4, no-engine-detected | Auto-recover: cd into the right project (or a data.candidates[] path) and novetest init — but never init a directory the operator didn't designate. |
Exit 4, adapter-* | Surface (engine-level issue, not a Nove Test bug). |
Exit 1, cli-error | Surface with the envelope. Do not retry blindly. |
Health-check pattern (agent reference)
(For interactive use, the sanity checks on the Installation page are enough.)
# 1) Binary on PATH
command -v novetest >/dev/null || { echo "novetest missing"; exit 1; }
# 2) Identity envelope
NOVETEST_OUTPUT=json novetest --version | \
jq -e '.ok == true and .schema == "novetest/v1"' \
|| { echo "version envelope malformed"; exit 1; }
# 3) Project store exists (if you have a workspace)
if [ -d ./.novetest ]; then
NOVETEST_OUTPUT=json novetest status | \
jq -e '.ok == true' \
|| { echo "status envelope malformed"; exit 1; }
fiPass = ready to drive. Fail = surface to operator.
When all else fails
novetest --versionto confirm the binary is sane.novetest --helpto confirm the verb exists.novetest status(afterinit) to see what's actually stored.- Re-run with
novetest --output json <verb>;errors[0]carries more detail than the text-mode line. - Search the issue tracker: https://github.com/Nove-Lab/Nove-Test/issues
- File a new issue with the JSON envelope and your OS / engine versions.
What to read next
- Exit-code -> meaning table → Understanding Results -> Exit codes.
- Per-engine setup → Supported Languages.
- Deeper verbs → Advanced Usage.