Skip to content
Talk to our solutions team

Errors and limits

Errors in this block come from five places: compiling a rule set, executing it, the rules.svc HTTP surface, the kis CLI, and document processing. This page lists all of them with the exact text you see. For symptom-first diagnosis, start at troubleshooting.

Two facts govern everything below:

  • One bad rule discards the whole execution. Abort-on-failed-evaluation is on unconditionally and every non-retracted rule is re-evaluated every cycle, so a rule that references a fact you did not supply kills output that other rules already produced. There is no partial result.
  • Every engine failure is an HTTP 400. A malformed request, an unknown rule set, a rule that crashed, and a service that has not loaded any rules yet are indistinguishable by status code.
OriginContent typeBody
Handler and engine failuresapplication/json{"error":{"code":"…","message":"…","context":…,"meta":…}}
Auth, tenant and config middlewaretext/plainBare message — Unauthorized, invalid tenant
Timeout middlewaretext/plaincontext-middleware-timed-out
Panic recoverytext/plain500 - Internal error
Unmatched pathnoneEmpty body with the status only

code is empty on every error except one. context is an internal field that serialises as {} or null. Trace and span IDs are never emitted, so an error response carries no correlation handle — the only one is the server-side log line.

StatusRaised byBody
200Successful executionThe rule set’s out map, unenveloped; {} when no rule wrote to out
400Request parse, missing name, engine not initialised, unknown rule set, any rule failure, cycle capJSON envelope
400Tenant middlewareinvalid tenant
400Config client unresolvedconfig client not initialised
401Auth middlewareUnauthorized
404Unregistered pathEmpty
405Wrong method on /ruleMethod Not Allowed
408X-Api-Timeout deadline exceededcontext-middleware-timed-out
500Panic in the handler500 - Internal error
500Result map could not be serialisedJSON envelope
503Request context cancelled — client disconnect or shutdown, and only when X-Api-Timeout was sentEmpty
MessageCodeStatusCause
json request parse failed due to {{err}}rules-json-request-parse-failed400Body is not decodable JSON, is not a JSON object, or is empty
name of rule to execute not given(empty)400name is missing, null, or an empty string. A non-string name is coerced to its string form and fails as an unknown rule set instead
rule engine not initialized(empty)400No rule set has ever loaded in this process
ruleset <name> not found: specified knowledge base name and version not exist(empty)400Typo in name, or the rule document never loaded
ruleset <name> not found: expression <grl text> is not on the clone table - E(EA(A(C(…))))(empty)400The rule set is poisoned — an edited rule body was re-delivered under an existing rule name
executing ruleset <name>: <engine error>(empty)400Any compile-time or run-time rule failure; see the tables below

The parse-failure message ships an unrendered {{err}} placeholder. The decoder’s actual complaint is in meta.error, never in message:

{"error":{"code":"rules-json-request-parse-failed","context":{},"message":"json request parse failed due to {{err}}","meta":{"error":"invalid character 'b' looking for beginning of object key string"}}}

Every other failure looks like this — no code, no meta:

{"error":{"code":"","context":null,"message":"executing ruleset invoice: error while executing rule R1. got non existent key log","meta":null}}

Rules compile at load time, not at execution time. A syntax error surfaces when the rule set is added — from the CLI on startup, from rules.svc during a metadata refresh where it reaches the log only.

MessageCause
got N error(s) in grl the scriptSyntax error, an unsupported construct, or two rules sharing a name in one rule set. Prefixed with <path>: when the rules were loaded from disk, bare when they arrived as a string
stat <path>: <err>The rule path does not exist or is not readable
reading <path>: <err>A rule file could not be read

The count is all you get. The parser collects one message per error — grl error on <line>:<col> <detail> for a syntax fault, rule entry <name> already exist for a duplicate name — but nothing unwraps that list, so a missing semicolon and a duplicate rule name produce the same sentence. From the CLI you at least get the file path; in the service you get adding rule failed <ruleset> <err> in the log and nothing on the wire.

The constructs that most often produce this error are all things the language does not have:

WrittenWhy it fails
findings.Add(Finding{...})No struct literals in the grammar. There is no way to record a finding from a rule
if cond { … } else { … };No control flow in then. Use two rules at different salience
Two conditions on separate lines in whenwhen takes exactly one expression; join with && or ||
A then statement without a trailing ;The semicolon is mandatory, including on the last statement
The same rule name in two filesEvery file loaded into one rule set shares a flat name space

All of these abort the execution and return no result. Over HTTP they arrive wrapped as executing ruleset <name>: … with status 400; on the CLI they fail that input file.

MessageCause
error while executing rule <R>. got non existent key <fact>A then referenced a name that was never bound
got left hand expression error. got non existent key <fact>A when referenced a name that was never bound
evaluating expression in rule '<R>' the when raised an error. got <detail>Condition evaluation failed; see the details below
this node identified as "<fact>" is not referencing to an objectDot access on a map fact. Use fact["Key"]
<fact>[string-><key>] is not an array nor mapTwo-level indexing into decoded JSON — the intermediate value is interface-typed
<var> is not an array nor mapIndexed a local variable holding a map or slice. Index the call directly instead
this node identified as "<obj>" have no function named <Fn>Misspelled or non-existent method on a bound object
this node identified as "DEFUNC" have no function named <Fn>An unqualified call matched no built-in — a helper needs its namespace, as in strings.Contains
this node identified as "<obj>" calling function <Fn> which … returns multiple values …The method returns (value, error). Affects out.JSON(), audit.JSON() and the document loaders
reflect: Call using int64 as type intAn integer literal was passed to a plain Go int parameter
reflect: Call using int64 as type float64A float parameter needs a decimal point — math.Min(10.0, 20.0)
reflect: Call using interface {} as type stringAn any-typed value reached a string parameter
reflect: call of reflect.Value.Type on zero ValueA comparison operand was nil — guard with out.Has(k) && out.Get(k) == v
reflect: Call using zero Value argumentA local variable holding a map or slice was passed to a function
the GruleEngine successfully selected rule candidate for execution after 5000 cycles, …The cycle cap tripped; a rule keeps re-qualifying
context canceledThe caller’s context was cancelled or its deadline expired

The multiple-values message is emitted across three lines with blank lines between them. Match on returns multiple values rather than on the whole string:

this node identified as "out" calling function JSON which
returns multiple values, multiple value
returns are not supported

Runtime panics are recovered and re-reported with the rule name — a panic in then as rule engine execute panic on rule <R> ! recovered : <panic message>, a panic in when as error while evaluating rule <R> ! recovered : <panic message>. Every reflect: row above arrives inside one of those two wrappers.

Everything below exits 1 and writes to stderr unless stated otherwise. A run terminated by a signal exits with 128 plus the signal number.

MessageCause
Error: 'rules' is not installed.kis rules, kis ocr, kis bbox and kis hocr2bbox proxy to a separately installed plugin binary. A second line names the package to install. The search order is the directory holding kis, ~/.kisai/bin, $KIS_PLUGIN_PATH, then $PATH
required flag(s) "rules" not set-r/--rules omitted. Required by kis rules, kis ocr page and kis ocr doc
loading rules: loading rules from <dir>: <file>: got N error(s) in grl the scriptA rule file failed to compile
no files matched pattern <glob>The -i directory and -p pattern selected nothing
no prefixes resolved (no positional args matched and no files matched pattern <p> in <dir>)kis ocr page/doc found no .json file with a matching .bbox sidecar
invalid --skip-pattern "<p>": <err>Malformed glob, validated before the run starts
ERROR: <path>: <err>One input failed. Printed inline as it happens — on stdout for kis rules, on stderr for kis ocr — and the batch continues
N file(s) failed to process / N prefix(es) failedThe final error from a batch that had at least one failure

Batch runs never stop early. Every input is attempted, each failure is printed as it happens, and the exit code is 1 if anything failed — so with --update the files that succeeded are already rewritten in place even though the command reports failure. See the CLI.

Document work runs on both surfaces. The CLI reads documents from disk — kis ocr page, kis ocr doc, kis rules --bbox / --with-bbox — and POST /rule carries them in the request body under bbox, keyed by the name a rule passes to bbox.Get, alongside the facts:

{
"name": "invoice-checks",
"total": 1200,
"bbox": {"page": {"pages": [{"words": []}]}}
}

bbox is reserved in the request body — its value is document input, not a fact — and the document bindings a rule sees are the same ones the CLI provides. Reading, parsing or analysing a document fails before any rule fires, on either surface, and the whole execution is discarded.

MessageCause
plugin bbox prepare: loading document <name>: <err>A supplied document could not be read, parsed or analysed. Over HTTP this arrives as a 400 error envelope
duplicate schema name "<n>" (from <a> and <b>)Two --schema/--schemas files share a basename — the rule-callable name is the filename without its extension
unsupported top-level type "<t>" (expected object or array)A schema’s top level is neither an object nor an array
array-typed schema must have an "items" objectAn array schema with no items
at least one input file is required (use positional args or -i flag)A kis bbox subcommand ran with no inputs
no input files providedSame, from the shared input resolver
invalid snippet definition "<s>": expected "name|start phrase|end phrase"A --snip value that did not split into three |-separated segments. An empty segment gets its own message naming it
table has no headerskis bbox table -H found a table with no header row
missing headers: <a> (found: <b>)Header validation failed against the detected table

The rows naming a flag or an input file are CLI-side; document load and analysis failures reach both surfaces. Over HTTP the schemas travel as a name-keyed object on the request rather than as files, so the basename collision cannot arise there — see service operations.

LimitValueConfigurableOn trip
Cycle cap5000 firing cyclesNo — fixed at 5000 on both surfacesExecution aborts, all output discarded, HTTP 400
Request body sizeNone
Request rateNone
Server-side request deadlineNone unless the caller sends X-Api-TimeoutPer request, by header408, and the request context is cancelled with it — the execution stops at the next rule boundary
HTTP read timeout15stimeouts.readConnection read fails
HTTP write timeout15stimeouts.writeThe response is aborted; the execution keeps running
HTTP read-header timeout5stimeouts.readheaderConnection read fails
Document pages, words, analysis timeNone

Configuration keys are covered in service configuration.

The cycle cap is a correctness backstop, not a timeout. A cycle counts only when a rule actually fires, so a rule set that blocks inside a helper never trips it, while one that fires cheap rules 5000 times does. Cancellation is checked between rules and between cycles, so a rule blocked inside a single helper call runs to completion whatever the caller’s deadline says.

These are the expensive ones, because nothing tells you.

What you wroteWhat happens
in.GetStr / GetInt / GetFloat / GetBool / GetMapAlways return the zero value, even for facts that are present. in.Has() works; reference facts directly instead
Rules with equal salienceFiring order is non-deterministic across runs. Give every rule in an ordered set a distinct salience
when out.GetStr("tier") == "gold" after another rule set itThe condition is memoised and never re-evaluated. The writing rule must call Changed("out")
A request field named out, in or findingsThe collector is bound last and wins. Your value is dropped without a word. Over HTTP the whole request body becomes the fact map, so this is easy to hit
A request field named bbox or audit that meant something elseBoth are reserved control keys, read before the fact map is built. bbox must be documents keyed by name and audit a boolean; neither is ever visible as a fact
in.Transform(key, "typo")Unknown transform names are a no-op — no error, no log, nothing written to memory
A malformed regex in any strings.* helperDegrades quietly: false, "", nil, or the input unchanged
audit.Log(...) in a rule served over HTTP, on a request that did not ask for the trailThe trail comes back only when the body sets "audit": true. Without it the response is the out map alone and the entries go unread. The output history, rules-matched count and duration are computed and never serialised on either surface
An out key called audit on a request that set "audit": trueThe trail is serialised last and overwrites it. Rename the output key
A kis ocr input whose .bbox sidecar is missingSilently skipped, both when the input came from a directory glob and when it was named as a .json/.bbox path. The only signal is no prefixes resolved when every candidate was dropped
Two tenants shipping a rule set with the same nameOne flat name space per process. The second delivery is refused and logs adding rule failed. Identical bodies mean both tenants run the first tenant’s rules; different bodies poison the rule set for both

A request field named util, num, strings, time, math, map or array collides the other way: your fact is bound after the helper namespace and replaces it, so the first rule that calls into that namespace aborts the execution with this node identified as "<name>" call function <Fn> is not supported for map.