---
generator: "vaultspec-marketing discovery"
title: "Scripting and automation - vaultspec-rag documentation"
description: "Pass --json to get machine-readable output instead of a formatted table."
url: "https://vaultspec.neve.md/docs/rag/automation.html"
component: "vaultspec-rag"
source_repository: "https://github.com/nevenincs/vaultspec-rag"
source_path: "docs/automation.md"
source_commit: "9093ad50f015110cf0061432da49ce859282c7d6"
date_modified: "2026-09-30T14:43:23+02:00"

---

[View as Markdown](<https://vaultspec.neve.md/docs/rag/automation.md>)

# Scripting and automation

Pass `--json` to get machine-readable output instead of a formatted table. The flag suppresses console formatting and writes exactly one JSON document to stdout, newline-terminated. Log lines still go to stderr or the service log. The default level is WARNING, so INFO lines appear only with `-v`. Add `2>/dev/null` when you want stdout alone.

Every command accepts `--json` except `server warmup`.

Read `ok`, `error`, and the process exit code, plus any per-item results the command returns. The [command reference](<https://vaultspec.neve.md/docs/rag/cli.html>) lists each command’s options. This page lists the [exit codes](<https://vaultspec.neve.md/docs/rag/automation.html#exit-codes-and-error-strings>).

Examples use the installed-tool form and call `vaultspec-rag` directly. If vaultspec-rag is a project dependency, prefix each command with `uv run`; see the [installation guide](<https://vaultspec.neve.md/docs/rag/installation.html>) for lane selection, including the standalone tool and no-install routes.

## Before you start

You need vaultspec-rag installed and a project indexed. See [Installation](<https://vaultspec.neve.md/docs/rag/installation.html>) for setup and [Getting started](<https://vaultspec.neve.md/docs/rag/getting-started.html>) for indexing your first project.

`search` and `index` talk to the running service. With `--port` unset, they read the port from the discovery record (see [Service discovery](<https://vaultspec.neve.md/docs/rag/service-discovery.html>)); pass `--port N` to target a specific service. An unreachable service returns the `port_unreachable` error rather than silently running the work in-process. Start the service with `vaultspec-rag server start`. See [Service mode](<https://vaultspec.neve.md/docs/rag/service-mode.html>) for the full walkthrough and [Service discovery](<https://vaultspec.neve.md/docs/rag/service-discovery.html>) for how the lookup resolves.

## The envelope shape

Every `--json` response is one JSON object with `ok` and `command`, except `install` and `uninstall`. Those two print the shared vaultspec envelope (`schema`, `status`, `data`); see [machine-readable output](<https://vaultspec.neve.md/docs/rag/installation.html#machine-readable-output>). For every other command, success includes `data`; failure includes `error` and `message` and may retain partial results in `data`. Error envelopes may also carry `port` or a `remediation` array.

`command` is an identifier for the operation rather than the command path you typed, and it is not always the subcommand’s name: `search` reports `search`, but `server jobs` reports `service.jobs`. It is not stable across a single command’s outcomes either. `index` reports `index` on the dry-run envelope, and one of its failure paths - an unreachable service port - reports `indexing` instead. So match on it only against a value you have observed for the exact command and outcome you are handling, and never assume the success and error envelopes of one command agree.

Success, with most per-hit fields elided:

**File contents**

```json
{
  "ok": true,
  "command": "search",
  "data": {
    "request_id": "fccd0f1fb63343078dbc8f683aa314be",
    "results": [
      {
        "id": "audit/graph-embedding-round36-audit",
        "path": ".vault/audit/graph-embedding-round36-audit.md",
        "title": "Round 36: Graph/Embedding Domain Audit",
        "score": 0.8315700888633728,
        "snippet": "...",
        "source": "vault",
        "doc_type": "audit",
        "feature": "gpu-rag-stack"
      }
    ]
  }
}
```

`data` carries more than the hits, and two of its fields answer questions a script would otherwise shell out for. `via` names what served the query, so a run that quietly fell back to the local store is visible without checking the service separately. `index_state` describes the index the query actually ran against: `indexed_count`, the root it was built from, and `target_matches`, which is false when the index belongs to a different tree than the one you asked about. `search_type`, `query` and `summary` echo the request, and `timing` breaks the run into phases.

Error. The capture below was taken against a service on port 8799, which is not the default: an unreachable service on a stock install reports 8766. The `port` field always echoes the port the CLI tried, so read it as the one your own run used rather than as a number to copy.

**File contents**

```json
{
  "ok": false,
  "command": "search",
  "error": "port_unreachable",
  "message": "Service on port 8799 is unreachable. The CLI will not silently run search locally; start the service or re-run with --allow-fallback (one local user only).",
  "port": 8799,
  "remediation": [
    "vaultspec-rag server status",
    "vaultspec-rag server start",
    "rerun with --allow-fallback (one user only)"
  ]
}
```

## Parse a search with jq

With that shape in hand, a search reduces to one `jq` path:

**Command**

```bash
vaultspec-rag search "graph rebuild race" --json \
  | jq -r '.data.results[].path'
```

`data.results` holds one object per hit. The fields you will usually want are `id`, `path`, `title`, `score`, `snippet`, and `source`. Each object also carries the retrieval metadata for its domain, such as `doc_type`, `feature`, and `date` for vault hits, or `language`, `line_start`, `line_end`, `function_name`, and `class_name` for code hits. Fields that do not apply to a hit are `null` rather than absent, so `jq` paths stay stable across domains.

A vault hit’s `snippet` is the passage that best answers the query, up to about 1,200 characters, and `line_start`/`line_end` give the file lines that hold it. `section` names the headings it sits under, such as `Implementation > Migration`. Read those lines, not the whole record:

**Command**

```bash
vaultspec-rag search "why was an idle timeout rejected" --type vault --json \
  | jq -r '.data.results[] | "\(.path):\(.line_start)-\(.line_end)  \(.section)"'
```

A vault index built before these fields existed reports `full_reindex_required` until it is rebuilt with `vaultspec-rag index --rebuild --type vault`; until then its hits carry `null` spans and a snippet cut from the start of the chunk.

## Detect success vs error

`ok` is `true` on success and `false` on error. Gate further work on it:

**Command**

```bash
out=$(vaultspec-rag search "graph rebuild race" --json)
if ! echo "$out" | jq -e '.ok' >/dev/null; then
  echo "$out" | jq -r '.error + ": " + .message' >&2
  exit 1
fi
```

That gate is right for the commands that answer a question, such as `search` and `index`. It is not the whole verdict for the diagnostics. `server doctor` sets `ok` from whether the dependencies and the service are ready, and computes its exit code from a second axis - a dead daemon, a declared-versus-observed mode mismatch, a version floor - so a host whose dependencies are all ready and whose install mode has drifted reports `ok: true` and exits `1`. Gate a diagnostic on its exit code, and read `ok` for what it says.

For index migration, a failed collection makes the command fail. Inspect `data.results` to distinguish copied collections from skipped ones. See the [migration result contract](<https://vaultspec.neve.md/docs/rag/cli.html#server-storage-migrate>).

The `message` field is written for people and gets reworded. Branch on the `error` code:

**Command**

```bash
case $(echo "$out" | jq -r '.error // empty') in
  port_unreachable)   echo "service unreachable; retry or start it";;
  local_store_locked) echo "another process holds the lock, aborting"; exit 1;;
  stopped)            echo "service not running, start it first"; exit 3;;
  "")                 echo "ok";;
esac
```

`port_unreachable` is retryable and exits `1`. For `search` you can also pass `--allow-fallback` to run in-process against the on-disk store under `.vault/data/`. `index` has no `--allow-fallback` flag: an unreachable service is a hard failure there, and its `remediation` array lists only `server status` and `server start`. To index locally on purpose, use `index --borrow-gpu`. It takes a borrower lease, pauses a compatible running service, and runs the work in this process.

## Indexing returns when the job is admitted, not when it finishes

With a service running, `vaultspec-rag index` hands the work to the service as a background job and returns immediately. `ok: true` means the job was admitted. It does not mean anything has been indexed:

**File contents**

```json
{
  "ok": true,
  "command": "index",
  "data": {
    "via": "service",
    "source": "combined",
    "outcome": {"ok": true, "partial": false, "status": "queued", "domains": {}}
  }
}
```

Note `"status": "queued"`. A script that reads that response as “indexing succeeded” will pass while indexing is still running, and will keep passing if the job later fails.

To watch the work itself, poll `vaultspec-rag server jobs --json` and read `data.jobs[]`, where each job carries `id`, `state`, and a `progress` object with `step`, `completed`, and `total`. `progress` can be `null`, for example before a job starts. To check the result instead, read the index counts with `status`, as [the CI example](<https://vaultspec.neve.md/docs/rag/automation.html#worked-example-gate-ci-on-index-health>) does.

## Worked example: gate CI on index health

Fail the build when the code index is empty, which usually means misconfigured ignore globs or source roots.

Ask `status` for the index counts. It reports what is stored, so it answers the question `index` leaves open: did the work finish?

**Command**

```bash
#!/usr/bin/env bash
set -euo pipefail

out=$(vaultspec-rag status --json)

if ! echo "$out" | jq -e '.ok' >/dev/null; then
  echo "status failed: $(echo "$out" | jq -r '.error')" >&2
  exit 1
fi

code_chunks=$(echo "$out" | jq '.data.code_count')

if [ "$code_chunks" -eq 0 ]; then
  echo "code index is empty; check ignore globs and source roots" >&2
  exit 1
fi
```

`status --json` also reports `vault_count` and `document_count`, the `installation` (role, hardware and compute capability), the project’s `features`, and storage details. Gate on whichever domain your project populates. A repository with no preprocessing hooks configured has `document_count` at `0` legitimately, and `document_count` can be `null` when the count is unavailable, so test for `null` before you compare it. The compute capability is a stable token such as `ready`, `cpu_only_build` or `not_applicable`, so a script can tell a broken GPU environment from a client that needs none.

## Exit codes and error strings

The exit code is the coarse signal; the `error` string names the specific failure.

| Code | Meaning |
| --- | --- |
| `0` | success |
| `1` | generic failure, including `port_unreachable` |
| `2` | usage error, such as an option the command does not accept |
| `3` | service stopped |
| `4` | service crashed or divergent |
| `5` | service starting: models loading, not yet serving |

`install` and `uninstall` add one meaning to `2`: the run completed but skipped a step it required consent for, such as the PyTorch configuration patch nobody was there to approve. A failed install exits `1` like any other failure. See [installation](<https://vaultspec.neve.md/docs/rag/installation.html#machine-readable-output>).

Code `5` is retryable; wait and re-run. The `error` field carries a code such as `port_unreachable`, `local_store_locked`, or `stopped`. The table above is the list of exit codes. The [CLI reference](<https://vaultspec.neve.md/docs/rag/cli.html>) lists each command’s options.

## Automatic re-indexing

A watcher keeps the index fresh, so a scripted pipeline does not need to call `index` on every run. The running service re-indexes incrementally as files change, and it is on by default. For headless or containerized deployments, set `VAULTSPEC_RAG_WATCH_ENABLED=0` to run pull-only. Inspect and tune it with the `vaultspec-rag server updates` verbs. See [Service mode](<https://vaultspec.neve.md/docs/rag/service-mode.html>) for the watcher in full and [Configuration](<https://vaultspec.neve.md/docs/rag/configuration.html>) for the environment variables.

## Where to go next

- [Getting started](<https://vaultspec.neve.md/docs/rag/getting-started.html>) walks through indexing a project and running a first search.
- [Service mode](<https://vaultspec.neve.md/docs/rag/service-mode.html>) covers running the background service and its watcher.
- [CLI reference](<https://vaultspec.neve.md/docs/rag/cli.html>) lists every command and its flags.
- [Configuration](<https://vaultspec.neve.md/docs/rag/configuration.html>) covers the environment variables and tuning knobs.

For anything else, see [Support and license](<https://github.com/nevenincs/vaultspec-rag/blob/9093ad50f015110cf0061432da49ce859282c7d6/README.md#support-and-license>) in the repo README.
