Skip to content

Troubleshooting ​

Start with error.code, not the exit code: several failures share an exit status, and PROPERTY_NOT_FOUND has no dedicated one. Every CLI command accepts --json:

json
{"ok": false, "error": {"code": "AUTH_REQUIRED", "message": "Google Search Console token is missing", "retryable": false, "next_action": "lv24-seo auth login --provider gsc"}, "meta": {"version": "0.5.2"}, "warnings": ["gsc: token missing"]}

MCP tools return the same envelope as structuredContent and set isError: true on failure. The desktop maps ok=false to a typed UI error.

First checks ​

bash
lv24-seo doctor --json
lv24-seo auth status --json
lv24-seo config show --json
# repeat the failing command with --json added

doctor checks Python, platform, config path, current-directory writability, the effective data directory and its scope, and token presence, with no network calls. Its top-level ok is fatal-only: Python and current-directory writability. Read checks[] for the rest.

Common exits: AUTH_REQUIRED and AUTH_INVALID (3), PROPERTY_NOT_FOUND (1), QUOTA_EXCEEDED and UPSTREAM_UNAVAILABLE (4), WRITE_DISABLED (2), CONFIRM_REQUIRED (1), INVALID_INPUT and CONFIG_INVALID (2), INTERNAL (1). TRUNCATED is reserved and not emitted: truncation is meta.truncated or limitations, and the command exits 0.

Missing tokens and provider authentication ​

AUTH_REQUIRED is local: no secret was found for that engine. Single-engine commands fail; aggregated surfaces continue with a warning such as gsc: token missing.

bash
lv24-seo auth status
lv24-seo auth set --provider gsc
printf '%s' "$TOKEN" | lv24-seo auth set --provider gsc --stdin

auth set accepts gsc, yandex, metrika, bing, crux, psi, smtp and telegram. Store the Webmaster token with auth set --provider yandex and the Metrika token with auth set --provider metrika or LV24_SEO_YANDEX_METRIKA_TOKEN. auth login --provider gsc|yandex opens no browser and writes nothing; it prints manual steps.

A 401 means the token is expired, revoked, malformed or from the wrong account — replace it. A 403 means the credential is valid but lacks permission: fix property or host access instead of re-authenticating. For Yandex, compare providers.yandex_webmaster.host_id with the exact id (https:example.com:443, not a URL); an unverified host looks like a 403.

The current engine has no OAuth refresh flow and no service-account auth. auth set --provider gsc stores one string sent as Authorization: Bearer; that is a Google access token, which normally lives about an hour. Store a fresh one per run. auth status reports present and the source (env, keyring, file) but never a value. An environment token is read-only: auth set and auth logout return WRITE_DISABLED while it is set.

data_dir and relative paths ​

A relative storage.data_dir written in seo.toml is anchored to that file's parent. init creates <config dir>/data and writes data_dir = "./data", so --config /srv/site/seo.toml uses /srv/site/data from any working directory. Paths are made absolute with os.path.abspath (~ expanded, .. normalised); symlinks are deliberately not resolved.

Two cases stay process-scoped on purpose: the built-in default (no [storage] block) is data relative to the working directory, and LV24_SEO_DATA_DIR overrides the file and resolves a relative value against the working directory.

doctor --json makes the scope visible: meta.data_dir is the effective directory, meta.data_dir_source is config when it is absolute and working directory when it is relative. A relative directory with a config file present adds a warning that names the config's folder. config show --json reports the same effective path. Set an absolute data_dir to pin the database.

Write token replay ​

A write apply with --confirm uses the workspace SQLite idempotency ledger when the workspace is initialised. Replaying the same token returns status: skipped with reason: duplicate and the provider is called once. A dry run opens no ledger and creates no database. Without a data directory the apply still runs, but the envelope warns that replay protection is off for the token's five-minute lifetime. A pending claim left by a crash has no TTL and is treated as in-flight until an operator releases it.

Sitemap too_large ​

Sitemap discovery uses a 50 MB per-document body budget, including the decompressed size of a gzip body. Ordinary pages keep the 5 MB cap. A document over budget is reported as too_large, not as an empty sitemap. The protocol cap is 50,000 URLs per sitemap; an index walk is limited to 20 documents and depth 2.

The budget is a body-size limit, not a time limit: a slow server can still hit the request timeout first. Split a large sitemap into several documents and reference them from an index. Raising the crawler URL budget does not raise the body budget.

Crawl memory and URL budget ​

crawl.max_urls defaults to 500. The CLI --max-urls accepts a positive integer; MCP seo_crawl_site clamps above 5,000 with a warning. The crawl is serial with robots Crawl-delay pacing; concurrency is reserved and has no effect.

Memory is bounded: page bodies are capped at 5 MB, retained page text at 20,000 characters, and the request budget is 1 + max_urls * (max_redirects + 1). word_count still uses the full visible text. If a crawl is too large or slow, lower --max-urls and crawl a section at a time. There is no resume or job queue in 0.5.2; the desktop has a 600-second crawl timeout and no cancel button.

--days limits ​

stats and report weekly reject --days above 3,660 with INVALID_INPUT and exit 2. Ten years is beyond any provider's retention. Lower --days, or query a longer history with explicit dates:

bash
lv24-seo gsc query --start 2026-01-01 --end 2026-06-30 --json

GSC search analytics lags 2–3 days, Yandex queries about 4 days, CrUX is a 28-day rolling p75 and PSI is a current lab run. --lag-days 2 is the report default.

Report diff has no snapshots ​

report diff needs two stored weekly snapshots for the same property. The default base is the second newest period and the default head is the newest. Typical errors:

MessageCause
report diff needs stored snapshots, but no store is configuredNo init, or a different data directory
need two stored weekly snapshots for <property>; found NFewer than two periods
no stored weekly snapshot for <label> period <period>An explicit --base or --head does not exist

Run init once, then report weekly twice for different periods. report diff is read-only and makes no provider calls. Metrics present in only one snapshot are unknown, not zero; comparable: false means the periods or GSC windows cannot be compared.

MCP client install ​

bash
lv24-seo install --client claude
lv24-seo install --client opencode
lv24-seo install --client vscode
lv24-seo install --client cursor
lv24-seo install --client vscode --print

The command merges the host configuration and prints what it changed.

SymptomFix
“not valid JSON” but the file looks validUTF-8 BOM, often from PowerShell 5.1; rewrite without BOM or delete and rerun
No such command 'mcp'Old build without mcp serve; upgrade to 0.5.2
GUI client cannot start the serverPut the absolute uvx path in command; the app does not inherit the shell PATH
VS Code lists nothinginstall writes mcpServers; VS Code uses servers; rename the key

OpenCode keeps old tools until opencode reload or a session restart; VS Code may need MCP: Reset Cached Tools after an upgrade.

The desktop does not retain MCP client entries; lv24-seo install is a CLI operation. If a merged host document is displayed, extract only the entry you own, because the document can contain other servers' tokens.

npm .cmd launcher on Windows ​

The npm package is a thin launcher. It forwards arguments to an installed lv24-seo binary or to uvx --from lv24-seo==<version> lv24-seo. On Windows, Node cannot spawn a .cmd shim with shell: false; it fails with EINVAL.

Version 0.5.2 ignores lv24-seo.cmd and .bat shims and prefers lv24-seo.exe or an extensionless binary before falling back to uvx. If the launcher still picks the wrong binary, set an absolute override:

bash
LV24_SEO_BIN=/absolute/path/to/lv24-seo npx @lv24/seo version --json

On PowerShell, set $env:LV24_SEO_BIN before running the launcher. LV24_SEO_PIN=0 opts back into the unpinned latest lookup. The launcher refuses to exec its own shim, so a PATH shadow cannot recurse.

Desktop engine is offline or version-gated ​

The shell resolves the engine in this order: LV24_SEO_BIN (absolute only), bundled engine/lv24-seo/, flat lv24-seo/, sibling lv24-seo/, then PATH only when LV24_SEO_ALLOW_PATH_ENGINE=1. It runs lv24-seo version --json and compares the result with MIN_ENGINE_VERSION, which is 0.5.2 in the current source.

An older engine is marked compatible: false; the UI treats it as offline and does not run the command. Nothing is downloaded automatically.

SymptomFix
Engine not foundReinstall, or set LV24_SEO_BIN to an absolute executable
Engine too oldInstall engine 0.5.2 or newer
External engine labelExpected when LV24_SEO_BIN is set

A stray lv24-seo on PATH is ignored unless LV24_SEO_ALLOW_PATH_ENGINE=1.

Portable, deb and rpm cannot auto-update ​

Auto-update is enabled only for Windows NSIS, macOS .app and Linux AppImage. deb, rpm, MSI and the portable ZIP are manual-only: they cannot safely replace the running installation. The Settings panel disables the in-app installer and links to the release page.

BundleUpdate path
Windows NSISIn-app signed update
Windows portable ZIPDownload and extract a new ZIP
Linux AppImageIn-app signed update
Linux deb or rpmUpgrade with the package manager

The portable build is detected from the sibling or bundled-flat engine source, so keeping lv24-seo/ next to the executable also keeps the updater in manual mode.

Updater signature failure ​

A signature failure is a security block, not a transient network error. The updater marks it non-retryable, blocks that artifact and points to the release page. Do not disable requireSignedVersion or retry the same artifact.

Download the release manually, verify it against SHA256SUMS.txt, and install it. The updater signature authenticates updates; it does not make the first manual download trusted. A bad signature can mean a corrupted download, a manifest/package mismatch or a key rotation. Report it rather than bypassing it.

Linux keyring and DBus ​

The engine uses the OS keychain only when the secrets extra is installed. The desktop sidecar bundles the keyring backends. On Linux, Secret Service needs a session bus; the desktop CLI runner inherits DBUS_SESSION_BUS_ADDRESS, XDG_RUNTIME_DIR, the XDG directories and TMPDIR, while filtering unrelated host variables.

SymptomFix
Keyring rejected the writeUnlock it, or set LV24_SEO_ALLOW_FILE_SECRETS=1 for the 0600 file fallback
No writable secret backendInstall lv24-seo[secrets] or enable the file fallback
Headless host, no session busUse LV24_SEO_* environment tokens

The fallback file is data/secrets.json, mode 0600 on POSIX. Windows uses ACLs. The desktop shows which source is active and that removing a file secret does not remove an environment variable.

macOS Gatekeeper and Windows SmartScreen ​

macOS builds are ad-hoc signed, not notarized. Gatekeeper warns on first launch. Use System Settings → Privacy & Security → Open Anyway, or right-click the app and choose Open. Do not disable Gatekeeper globally.

Windows builds have no Authenticode signature. SmartScreen, Smart App Control or antivirus software may warn or block the installer or the portable executable. Choose More info → Run anyway only after verifying the SHA-256 against SHA256SUMS.txt. Do not disable those protections system-wide. If you need a signed build, build from source and sign it with your own certificate.

Updater says UPDATE_ENGINE_BUSY ​

The engine sidecar lives inside the installed resource directory. Before an in-app update the shell stops only the sidecar PIDs it registered; an engine launched from a terminal or another app copy is not killed. If that process keeps the installed file locked, the install is blocked with UPDATE_ENGINE_BUSY.

Close the terminal or app run that holds lv24-seo, wait for it to exit, and press Download and install again. The error is retryable; do not try to delete files inside the installed resource directory.

Logs and diagnostics ​

Logging is opt-in. LV24_SEO_LOG_LEVEL=INFO (or DEBUG, WARNING, …) makes the CLI and lv24-seo mcp serve install the redacting handler. An unset variable leaves the caller's logging configuration untouched; importing the library or calling create_server() never replaces root handlers. Output is one JSON line per record when stderr is not a terminal (pipe, scheduler log, MCP host) and a plain line otherwise. The MCP transport logs to stderr only; stdout stays the protocol. The stable diagnostic interface is still the --json envelope, not a log file.

The desktop has a log directory, a data directory and an export directory. Their exact paths are reported by shell_status and shown in the Configuration screen; do not guess them from the bundle identifier. The diagnostics bundle is user-triggered and contains versions, paths, doctor checks, redacted effective config, environment variable names and recent non-secret job errors. It is never uploaded automatically.

Before posting an envelope, remove tokens, cookies, Authorization headers, LV24_SEO_WRITE_SECRET, .env contents, data/secrets.json, service-account JSON and customer queries. CLI input validation can echo the value you passed, and a URL query may contain key=, token= or signature=.

text
lv24-seo version:
OS / Python:
command:
expected:
actual (redacted --json envelope):

Report security problems through the private advisory described in SECURITY.md, not a public issue.

See Security and privacy for the write gates and Releases and updates for updater behaviour.

Source: _source/engine-troubleshooting.md, _source/engine-releases-0.5.2.md, _source/desktop-releases-0.2.2.md, _source/desktop-releases-0.2.1.md, _source/desktop-releases-0.2.0.md, _source/desktop-security.md, _source/desktop-architecture.md.