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 false

Until 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: false

verbatra 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.

Edit on GitHub