StringLane

    Type to search titles, sections and body text across all 58 pages.

    Reference

    Exit Codes and the JSON Report

    What each of the four stringlane exit codes means, the shape of the --json envelope, and the machine-readable refusal codes apply can return.

    Last updated

    Exit codes#

    CodeMeaning
    0No issues
    1The project has localization issues
    2The command could not run
    3Something stringlane depends on did not answer

    The separation of 1 and 2 is load-bearing. A missing stringlane.yaml, an unreadable file or a bad flag is not the same event as a genuinely broken translation, and a pipeline that treats them alike tells you the wrong thing on a bad morning.

    3 narrows 2 further, and today only one thing produces it: the npm registry gave no answer to stringlane upgrade. The command itself was fine and your project is not implicated, so calling it 2 would say you invoked it wrong. Nothing about your translations can produce a 3, and neither can the background update check: that one leaves the exit code untouched whatever the registry does, so a check job never sees one.

    --fail-on decides which findings the exit code is computed from, in both directions: naming a code that is present exits 1, even for an info finding, and naming codes that are all absent exits 0. It cannot suppress a 2 or a 3.

    Per-command variations:

    • plan exits 0 even when there is a great deal to translate, because a plan is a list rather than a gate. It exits 1 only when a file failed to parse, since a plan built from an unreadable file is incomplete rather than empty.
    • validate exits 1 when the candidates have problems.
    • check exits 2, whatever --fail-on says, when sourceStamps: true is set and .stringlane/source-stamps.json is missing, unreadable, malformed or not a plain file; when the baseline holds a translation_stale or translation_unconfirmed entry; and when --fail-on names one of those two codes while sourceStamps is off.
    • apply exits 0 when everything was applied, 1 when the candidates have problems, 2 when it could not run, which includes every refusal below and, with sourceStamps: true, a stamps file that is missing, unreadable, malformed or not a plain file.
    • update exits 2 before any credential is read when --parallelism or STRINGLANE_AI_PARALLELISM is not a whole number from 1 to 8.
    • pluralize exits 0 when the forms were written, previewed or declined, 2 when it refused or could not run.
    • accept exits 0 when the stamps were written or you declined, 2 when it refused or could not run, including a project without sourceStamps: true.
    • upgrade exits 0 whether you are current or behind, since being behind is not an error, and 3 when the registry gave no answer.

    The JSON envelope#

    stringlane check . --json --no-scan
    {
      "protocolVersion": "1",
      "command": "check",
      "status": "issues",
      "result": {
        "kind": "single-origin",
        "locales": [
          "de",
          "en",
          "fr"
        ],
        "baseLocale": "en",
        "baseLocaleSource": "detected",
        "totalKeys": 8,
        "completionPercent": 96,
        "localeCoverage": [
          {
            "locale": "de",
            "totalCells": 9,
            "filledCells": 9,
            "missingCells": 0,
            "totalKeys": 8,
            "filledKeys": 8,
            "missingKeys": 0
          },
          {
            "locale": "en",
            "totalCells": 9,
            "filledCells": 9,
            "missingCells": 0,
            "totalKeys": 8,
            "filledKeys": 8,
            "missingKeys": 0
          },
          {
            "locale": "fr",
            "totalCells": 10,
            "filledCells": 9,
            "missingCells": 1,
            "totalKeys": 8,
            "filledKeys": 7,
            "missingKeys": 1
          }
        ],
        "localizationFiles": [
          "res/values-de/strings.xml",
          "res/values-fr/strings.xml",
          "res/values/strings.xml"
        ],
        "parseErrors": [],
        "loadIssues": [],
        "configWasAbsent": false,
        "configState": "present",
        "format": "android-xml",
        "detectedConfig": {
          "format": "android-xml",
          "path": "res"
        },
        "issues": [
          {
            "key": "app_name",
            "locale": "de",
            "severity": "warning",
            "code": "untranslated",
            "message": "Key \"app_name\" in \"de\" appears to be untranslated (same as base)"
          },
          {
            "key": "app_name",
            "locale": "fr",
            "severity": "warning",
            "code": "untranslated",
            "message": "Key \"app_name\" in \"fr\" appears to be untranslated (same as base)"
          },
          {
            "key": "greeting",
            "locale": "fr",
            "severity": "warning",
            "code": "punctuation_mismatch",
            "message": "Key \"greeting\" in \"fr\" needs a non-breaking space before \"!\" but has a plain space before \"!\"",
            "detail": {
              "expected": "a non-breaking space before \"!\"",
              "actual": "a plain space before \"!\""
            }
          },
          {
            "key": "item_count",
            "locale": "fr",
            "severity": "warning",
            "code": "missing_key",
            "message": "Plural quantity \"many\" for \"item_count\" is missing in \"fr\"; \"fr\" uses \"many\" only for exact millions, so it shows only for counts like 1,000,000",
            "quantity": "many"
          }
        ],
        "issueCounts": {
          "error": 0,
          "warning": 4,
          "info": 0
        }
      },
      "warnings": []
    }

    That is the real output of CLI 0.9.0 on a three-locale Android project, exit 1: a French plural missing its many form is a warning, because French uses many only for exact millions.

    Envelope fields#

    FieldMeaning
    protocolVersionCurrently "1". The contract to build against
    commandWhich command produced this
    statusThe outcome, matching the exit code family
    resultThe command's own payload, shaped per command. update --json, for one, carries retranslatedCount: how many applied items replaced a translation that was already there
    warningsNon-fatal notes about the run itself, not about your translations

    Coverage fields#

    baseLocale is the source language. baseLocaleSource says where it came from: declared when baseLocale: in stringlane.yaml names it, detected when StringLane guessed it from your file names.

    Each localeCoverage row carries two counts. totalKeys, filledKeys and missingKeys count keys out of the same total in every locale, and a key is filled once every form the language needs holds a value; the text report's rows show these. totalCells, filledCells and missingCells count strings: a plural key contributes one cell per form the language requires, which is why a locale requiring one, few, many and other can show a higher totalCells than one requiring two forms. completionPercent is derived from cells across the project, and the text report prints it as N% of strings filled.

    Issue fields#

    FieldMeaning
    keyThe translation key
    localeThe locale the finding is about
    severityerror, warning or info
    codeStable identifier: see the validation codes
    messageHuman-readable, and the string the terminal prints
    quantityPresent on plural findings: the CLDR category
    positionWith check --positions: the file and line the finding sits on
    detailPresent on some codes: what was expected and what was found

    code is the field to branch on. message is written for a person and is allowed to be reworded.

    Refusal codes#

    apply and the writing MCP tools report a machine-readable reason when they decline. Nothing is written on any of them.

    CodeMeaningWhat to do
    STALE_PLANA file the plan fingerprinted changed since the plan was made (every locale file the project loads, not only the targets, plus the .stringsdict beside each .strings file, stringlane.yaml and .stringlane/metadata.yaml), or the plan names a file the project no longer manages (deleted, renamed or broken since, or a plan edited by hand). validate refuses the second kind tooRe-run stringlane plan
    PROJECT_BUSYAnother StringLane process holds the per-project lock: another apply, or from desktop 1.3.0 the desktop app savingWait, or --force-unlock if you know the holder is dead
    PATH_OUTSIDE_PROJECTA path resolved outside the canonical project rootCheck for a symbolic link in the repository
    INVALID_PROJECTThe project could not be read as a StringLane projectRun stringlane init, or check stringlane.yaml

    WRITE_FAILED is returned when the write itself could not happen: the project directory is not writable, the filesystem is read-only, or there is no space. Nothing is written on it either.

    Two issue codes, unused_key and undefined_key, are produced only by the source-code scan, so check --no-scan --fail-on unused_key is refused rather than accepted: with the scan off nothing can emit that code, and a gate that can never fire looks exactly like a clean project.

    A candidate naming a key and locale the plan does not contain is refused rather than ignored. Quietly dropping an agent's typo produces a missing translation nobody ever notices.

    Frequently asked questions

    What does stringlane exit code 2 mean?
    The command could not run: a missing or invalid config, an unreadable project, a bad flag. It is deliberately distinct from exit 1, which means the command ran and found localization issues, and from exit 3, which means the command was fine but something it depends on did not answer. A broken gate and a failing gate are different answers.
    Is the stringlane --json output stable?
    The envelope carries protocolVersion "1" and is the contract to build against. Pin the CLI version in CI anyway: at 0.x the command surface is not promised, and the version number is how that is said.

    Free ยท In your coding agent

    Run the same checks from your coding agent

    Paste this into Claude Code, Cursor, Codex or five other agents. It sets up the CLI and MCP server for this project, proposing each command and waiting for your yes.

    Read https://stringlane.app/docs/cli/agent-setup.md and set up StringLane for this project.

    Or install it yourself:

    npm install -g @stringlane/cli