Flow validation warnings
Runtype validates a Flow every time you save it, and every time you validate or create one through the API. Some checks catch configuration mistakes that pass schema validation but fail at runtime. These surface as non-blocking warnings so you can fix them before you dispatch, not after a run fails.
How validation warnings work
Validation runs at save time in the dashboard, when you call the validate endpoint (POST /v1/public/flows/validate) or the validate_flow MCP tool, and when you create or update a Flow. The response is a single envelope:
Warnings do not block a save (valid stays true when there are no errors), but each one predicts a failure that would otherwise only show up at dispatch time. Read warnings[].code to identify the problem and warnings[].message for the exact fix. The path and step fields point to the step that triggered it.
The rest of this page catalogs each code, when it fires, and how to fix it. Most land in warnings[]. Codes that are pure optimization hints (they never predict a failure) land in recommendations[] instead, and are noted as such below.
List Records output read with object access
Code: LIST_RECORDS_OBJECT_ACCESS
A list-records step returns an array of records, even when only one record matches. If a later step reads a field with object-style access ({{customers.title}}) instead of indexing into the array first ({{customers.0.title}}), the reference resolves to undefined at runtime.
The validation message names the variable, the field, and the correct fix:
A get-record step always returns a single object, so object-style access is correct there and this warning does not fire. A deprecated retrieve-record step is checked under whichever of the two it resolves to (see Migrating from retrieve-record). See Using record steps (get/list/upsert) for the full return-shape rules.
Deprecated step type: retrieve-record
Code: DEPRECATED_STEP_TYPE
retrieve-record is deprecated in favor of Get Record and List Records. The step still works exactly as before: Runtype resolves an ID-based lookup as get-record and a type/name/filter lookup as list-records at execution time. This warning is a non-blocking reminder to migrate the step the next time you edit the Flow.
Upsert-record source variable is not a JSON object
Code: UPSERT_RECORD_SOURCE_NOT_JSON
An upsert-record step’s sourceVariable must reference a JSON object, because the record metadata column is JSONB. The most common mistake is feeding a prompt step’s plain-text output (any responseFormat other than json) straight into upsert-record without a contentField. That produces a string, which fails at runtime.
The validation message names the offending prompt step and gives you three ways to fix it:
Store-vector source variable is unresolved
Code: STORE_VECTOR_SOURCE_UNRESOLVED
A store-vector step resolves its vectorsSource by variable lookup. If the named variable is not produced by any earlier step and is not a declared flow input, the step throws Could not resolve vectors at dispatch time.
The validation message names the missing variable and the usual source for it:
Tool call strategy “required” on a multi-step prompt
Code: TOOL_STRATEGY_REQUIRED_MULTISTEP
Setting toolCallStrategy: "required" on a prompt step forces the model to call a tool on every step. Unless the turn is capped to a single tool call, the model can never return a final text answer, so the output comes back empty. This fires when maxToolCalls is unset (the runtime default allows several calls), set above 1, or the prompt runs in a multi-turn loop.
The validation message spells out the safe configurations:
See Using prompt steps for prompt-step tool configuration.
Conditional compares an unquoted template against a string
Code: CONDITION_UNQUOTED_TEMPLATE_COMPARISON
A condition or when predicate is JavaScript that runs after template substitution. If you compare an unquoted {{...}} placeholder against a string literal ({{analysis.health}} === 'watch'), a non-numeric value substitutes in as a bare identifier (healthy === 'watch') and throws a ReferenceError at runtime.
The validation message shows the offending snippet and the fix:
Quote the placeholder ('{{analysis.health}}' === 'watch') for string comparisons, or compare numerically when the value is a number. See Using conditional steps for more on condition expressions.
System prompt embeds a per-run variable
Code: CACHE_VOLATILE_SYSTEM_PROMPT (appears in recommendations[])
A prompt step’s system prompt embeds a per-run temporal variable ({{_now}}, {{_execution.*}}, or {{_schedule.*}}). Because that value changes on every execution, the system prompt is different each run, so Runtype’s automatic prompt caching cannot reuse the cached prefix across requests. This is a non-blocking optimization hint, not an error. The flow runs exactly the same. It just caches worse than it could.
The recommendation message names the offending variable and the fix:
Move the changing value into the user prompt or a later step so the system prompt stays byte-identical across runs. See Prompt caching for how automatic caching works.
Record step writes a field the collection schema does not declare
Code: RECORD_FIELD_NOT_IN_SCHEMA
When an upsert-record or update-record step targets a record type that is a schematized collection, Runtype checks the fields the step writes against the schema. If the step writes a field the schema does not declare and the schema closes additionalProperties (sets it to false), the write is badged in warn mode and rejected in enforce mode at runtime. This warning surfaces that at save time.
Runtype can only check fields it can read statically: an update-record step’s updates map keys, and an upsert-record step’s contentField. It fires regardless of the collection’s validationMode, because the schema is the collection’s declared data model either way. Collections whose schema leaves additionalProperties open accept extra fields, so this warning does not fire for them.
The validation message names the field, the collection, and the schema version:
Add the field to the collection schema (see Defining a schema), or remove it from the step’s updates / contentField.
Record step leaves a required schema field unmapped
Code: RECORD_REQUIRED_FIELD_UNMAPPED (appears in recommendations[])
When an update-record step uses mergeStrategy: "replace" (which overwrites the record’s whole metadata, so the fields it writes are the complete record), Runtype checks that every field the target collection’s schema marks required is set. Any required field the step never maps produces this recommendation, so you notice the gap before the replaced record fails schema validation.
This is a recommendation, not a warning: it lands in recommendations[], and it fires only for complete (replace) mappings. A merge or deep-merge write can inherit the required field from the existing record, so it never triggers this. Like the field warning above, it is independent of the collection’s validationMode.
The recommendation message names the required field, the collection, and the schema version:
Add the required field to updates, or drop it from the schema’s required list (see Defining a schema).
Next steps
- Debugging flows to trace runtime errors after a Flow dispatches
- Using record steps (get/list/upsert) for record step return shapes and source variables
- Using conditional steps for writing safe condition expressions
- Flow variables and templates for variable syntax and scope