Troubleshooting
Symptoms, causes, and fixes for the errors verbatra actually raises.
Each entry below matches a real error code or message. Whole-run failures carry a stable code
(SdkError); branch or search on the code, not the message text. For how failures map to CLI exit
codes, see CI and exit codes; for the full code table, see the
SDK reference.
pnpm blocks a dependency's install scripts (ERR_PNPM_IGNORED_BUILDS)
Symptom: pnpm add -D @verbatra/cli prints its install summary and then exits 1:
[ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: @google/genai@2.15.0, protobufjs@7.6.5
Run "pnpm approve-builds" to pick which dependencies should be allowed to run scripts.pnpm also writes a pnpm-workspace.yaml into your project holding an unanswered choice:
allowBuilds:
'@google/genai': set this to true or false
protobufjs: set this to true or falseUntil you answer it, every later pnpm command in that project fails the same way, including
pnpm exec verbatra init.
Cause: pnpm does not run a third-party dependency's install scripts unless you allow them.
@google/genai and its transitive protobufjs declare such scripts, and the Gemini SDK is
installed whichever provider you configure, so every pnpm install path hits this. Nothing in
verbatra declares an install script of its own, and npm and yarn install the same package without
a prompt. The install itself did complete: node_modules/.bin/verbatra already works.
Fix: answer the choice once. Interactively, run pnpm approve-builds and approve neither
entry. Non-interactively, which is what you want in CI, write the answer before you install by
putting this in pnpm-workspace.yaml at the project root:
allowBuilds:
'@google/genai': false
protobufjs: falseverbatra does not need those scripts, and this repository declines both. With the answer in place
pnpm add -D @verbatra/cli exits 0 and later pnpm commands run normally. The package versions
in the message track the release; the fix does not change.
The configuration is invalid (CONFIG_INVALID)
Symptom: The verbatra configuration is invalid: ..., listing one issue per field.
Cause: the config was found but fails schema validation: a missing required field, a
files.pattern without the {locale} token, a source locale listed in targetLocales, or an
unrecognized top-level key. An unrecognized key gains the hint API keys are read from the environment, not the config: the schema is strict precisely so a secret cannot hide in a
committed file.
Fix: correct the field the message names. See The config file for the
full schema. If the message is No verbatra configuration found... (CONFIG_NOT_FOUND), run
verbatra init or pass --config <path>.
No adapter for the format (UNKNOWN_FORMAT)
Symptom: No adapter is registered for format "..." followed by the supported list.
Cause: the config's format is not one of the eight registered format ids. This is checked
before any file is read.
Fix: use one of the ids from Formats, for example i18next-json or xliff.
Requested locale is not configured (UNKNOWN_LOCALE)
Symptom: Requested locale not in the configured target locales: ... Configured targets: ...
Cause: a --locales value (or an SDK locales/locale input) names a locale that is not in
targetLocales. Locale filters select from the configured list; they never add to it.
Fix: add the locale to targetLocales in the config, or fix the typo in the filter.
The source file is missing or unparseable (SOURCE_UNREADABLE, SOURCE_INVALID)
Symptom: The source locale file was not found at <path>. or The source locale file at <path> could not be read: ...
Cause: files.pattern with the source locale substituted does not point at an existing file
(SOURCE_UNREADABLE), or the file exists but the adapter rejects it, for example invalid JSON or
a structural problem (SOURCE_INVALID, wrapping the adapter's message).
Fix: check the path the message prints; it is the pattern resolved against the working
directory, so a wrong --cwd is a common cause. For structural rejections, see the
INVALID_STRUCTURE entry below.
A wildcard in the pattern is another cause: files.pattern is a literal path, not a glob, so
public/locales/{locale}/*.json is looked up as a file named *.json. See
Namespace layouts.
Missing API key (PROVIDER_CONSTRUCTION_FAILED)
Symptom: Failed to construct provider "anthropic": The ANTHROPIC_API_KEY environment variable is not set. (or the matching variable for your provider).
Cause: the provider reads its key from the environment when it is constructed, and the named variable is unset or empty. Error messages name the variable but never contain a key value, and keys are never read from the config or CLI arguments.
Fix: set the variable the message names. translate, watch, and studio load .env and
.env.local from the working directory; the SDK does not, so in your own script use
node --env-file=.env or export the variable. openai-compatible only raises this when the config names an apiKeyEnvVar that is
unset; without one it falls back to a keyless placeholder for local servers.
Rate limits, timeouts, and auth failures mid-run (RATE_LIMITED, TIMEOUT, AUTH_FAILED)
Symptom: the run completes, but some keys were not translated: they appear under a locale's
providerFailures, with a SUB_BATCH_FAILED notice carrying the provider code. The locale is
reported as partial when some keys did land and failed when none did, and the command exits
1 either way.
Cause: a provider call failed after construction: HTTP 429 (RATE_LIMITED), a network or
request timeout (TIMEOUT), or HTTP 401/403 (AUTH_FAILED, an invalid or revoked key).
Fix: nothing is lost. The affected keys keep their prior lock baseline and are picked up again
on the next run, so for RATE_LIMITED or TIMEOUT simply re-run later, and use
--locales <locale> to work through the target locales one at a time when the limit is a strict
per-minute or per-day quota. AUTH_FAILED does not
resolve by retrying: replace the key behind the environment variable. A smaller maxBatchSize
also reduces the blast radius of a single failed request. When TIMEOUT is the rule rather than a
hiccup (a slow local model, or large batches), raise requestTimeoutMs in the provider's options:
it bounds every request at two minutes by default. See
Request timeout.
The lock file is corrupt (LOCK_FILE_INVALID)
Symptom: The lock-file at <path> is not valid JSON., ... has an unexpected shape.,
... has version N, but this version of verbatra supports version 1., or ... exceeds the maximum allowed size ...
Cause: verbatra.lock.json was hand-edited, truncated, produced by an incompatible version,
or damaged in a merge.
Fix: restore the file from version control; that keeps every baseline intact. Deleting it also clears the error, but loses the record of which source version each translation came from, so source edits made before the deletion are no longer detected as stale. See The lock file.
A locale file could not be written (TARGET_UNWRITABLE)
Symptom: a locale fails with Could not write the locale file locales/de.json (EACCES). Check the write permissions on the containing directory, then run again.
Cause: the target file, or the directory holding it, cannot be written by the user running verbatra: no write permission, a directory that does not exist, a read-only mount, or a full disk. Locale files are written atomically through a temporary file in the same directory, so a directory that is unwritable fails the write even when the locale file itself looks fine.
Fix: the message names the target relative to your working directory and the file-system code
behind the failure. Fix that (chmod the directory, create it, remount read-write, free space) and
re-run. Only the affected locale fails; the others still run, and the failed locale keeps its
previous file and lock baseline, so nothing is half-written.
Another process holds the write lock (LOCK_CONTENDED)
Symptom: Could not acquire the write lock at <path>: another process may be holding it. If no verbatra process is currently running, this lock file was likely left behind by one that was killed; delete it and retry.
Cause: writes to a locale are serialized through a per-locale lock file. A concurrent
translate, watch, import, or Studio write is holding it, or a killed process left the lock
file behind.
Fix: exactly what the message says: wait for the other run to finish, or, if none is running,
delete the lock file at the printed path and retry. To change how long a run waits before giving
up, pass --lock-timeout <seconds> to translate or
watch; it defaults to 600 seconds.
Dotted keys or YAML keys collide (INVALID_STRUCTURE)
Symptom: A dotted key and a nested key path resolve to the same path. (or the literal-leaf
variant), or for YAML: A mapping key is a map or sequence (expected scalar keys).
Cause: two entries in one file resolve to the same effective key, for example a literal
"a.b" key next to a nested a: { b: ... }, which verbatra rejects rather than silently dropping
one; or a YAML file uses a composite mapping key, which has no faithful string form.
Fix: rename one of the colliding keys, or replace the composite YAML key with a scalar. When
the file is your source locale, this surfaces wrapped in SOURCE_INVALID. See
Formats for each format's key rules.
The run stopped short of some keys (token budget)
Symptom: a BUDGET_TOKENS_EXCEEDED notice: The run's cumulative token usage (N) reached the configured budget of M tokens (behavior: ...); with budgetBehavior: "stop", keys also appear
under budgetWithheld.
Cause: the configured maxTokens ceiling was crossed. With "warn" (the default) the run
continues unchanged and does not change the exit code; with "stop" every key not yet attempted is
withheld for the rest of the run, which leaves that locale partial (some keys landed) or failed
(none did), and both exit 1.
Fix: this is the guardrail working. Withheld keys keep their baselines and are translated on
the next run; raise maxTokens or switch to "warn" if you want single-run completion. A budget
against DeepL or a dry run reports supported: false and never trips, because no token usage
exists to measure.
Studio: the port is already in use
Symptom: port 5849 is already in use (or your --port value).
Cause: another process, often an earlier Studio instance, is bound to the port. Studio defaults to 5849 on 127.0.0.1.
Fix: stop the other process, or start Studio on another port: verbatra studio --port 6000.
Studio: @verbatra/studio is not installed
Symptom: Verbatra Studio requires @verbatra/studio. Install it with: pnpm add -D @verbatra/studio
Cause: @verbatra/studio is a separate package the studio command loads dynamically, so the
rest of the CLI works without it.
Fix: install it as the hint says, then re-run verbatra studio. The hint spells out the pnpm
command, so if you install with pnpm, see
pnpm blocks a dependency's install scripts
first.
Studio: retranslate and translate actions are missing
Symptom: editing translations works, but the retranslate and translate-pending actions do not appear.
Cause: provider spend is a capability you grant at startup. Without it, the spend-gated methods are not registered at all; local file editing is always on and needs no flag.
Fix: restart Studio with verbatra studio --allow-spend, or set
VERBATRA_STUDIO_ALLOW_SPEND=1 (also true, yes, or on). Rapid repeated actions can also hit
Studio's own throttle, METHOD_RATE_LIMITED: Too many calls to this method; wait before retrying.; that clears on its own. See Review in Studio.
Undoing a run (no error code)
Symptom: a run produced translations you do not want, and you need the previous state back.
Cause: verbatra rewrites locale files in place and updates the lock file. There is no undo command and no backup; version control is the recovery mechanism.
Fix: revert the locale files and verbatra.lock.json together. Reverting only the locale files
leaves the lock claiming the reverted translations are current, so nothing retranslates them and
check and diff still report the locale as clean. See
Recovery and rollback.