Flow validation warnings
Runtype checks a Flow’s configuration for mistakes that pass schema validation but fail, or produce a silently wrong result, at runtime. This page lists the codes those checks report most often, what triggers each one, and how to fix it. It also covers the advisories that Agent, Product, and Surface saves return for configurations that cannot run.
How validation results are reported
Validation sorts each issue into one of three lists:
errors[]: the configuration can’t be saved. A save that has any error fails.warnings[]: the Flow saves, but the configuration predicts a runtime failure or a silently wrong result.recommendations[]: the Flow saves and runs as written. The issue is an optimization or a design improvement.
An Agent save returns its advisories in the save response’s _warnings field instead. Each section on this page names the list that its code appears in.
A validation result has this shape:
valid is true when errors is empty. Read code to identify the problem and message for the suggested fix. The path and step fields identify the step that triggered the issue. An issue about the whole Flow has no step.
Where you see validation results
You get the full result, with all three lists, from these places:
- The Flow editor in the dashboard. The editor re-checks the Flow as you edit, and the toolbar shows a count of errors, warnings, and recommendations. Click the count to open Issues in this flow. Each item shows the message, the step name (or Whole flow), and the code. Click an item to select the step that it names.
- The validate endpoint,
POST /v1/public/flows/validate. - The SDK
validate()method on a Flow builder. - The
validate_flowMCP tool.
When you create or update a Flow through the API (POST /v1/flows or PUT /v1/flows/{id}), Runtype runs the same checks. A successful save returns only the Flow, without warnings or recommendations. To see warnings for a Flow that you save through the API, call the validate endpoint first.
A create or update that has errors returns HTTP 400. The errors are in details, not in errors, and step names the step of the first error:
Validate a Flow before you save it
The validate endpoint checks a Flow definition without creating it. Send the Flow’s name and flowSteps:
Replace YOUR_API_KEY with your Runtype API key. A body that is not valid JSON returns HTTP 400 with the code INVALID_JSON.
With the TypeScript SDK, build the Flow and call validate():
Checks that need authentication
The Authorization header is optional on the validate endpoint. Without it, Runtype runs every check that reads only the Flow definition and skips the checks that look up resources in your account. The skipped checks include TOOL_NOT_FOUND, FLOW_REFERENCE_NOT_FOUND, AGENT_NOT_FOUND, QUESTION_SET_UNRESOLVED, and MODEL_NOT_ENABLED.
The context object reports what ran:
authenticated:truewhen the request carried valid credentials.accountChecksPerformed:truewhen Runtype checked the Flow’s references against your account.accountChecksSkipped:truewhen the Flow references account resources but the request was not authenticated.
When Runtype skips account checks for a Flow that references account resources, warnings[] also contains ACCOUNT_CONTEXT_UNAVAILABLE. To run those checks, send the request with your API key.
Variable reference warnings
Codes: UNDECLARED_VARIABLE_REFERENCE, MALFORMED_VARIABLE_REFERENCE, and UNKNOWN_VARIABLE_NAMESPACE (warnings)
These warnings report a template reference that can’t resolve at runtime:
UNDECLARED_VARIABLE_REFERENCE: no earlier step’s output variable and no Flow input has the referenced name.details.availableVariableslists the names that are declared at that point in the Flow.MALFORMED_VARIABLE_REFERENCE: a reference uses single braces, such as{customerName}, instead of{{customerName}}.UNKNOWN_VARIABLE_NAMESPACE: a reference uses a prefix other thanflow:orsecret:.
For causes and fixes, see Reference warnings.
Structural errors that block a save
The following codes report a Flow structure that Runtype can’t save. Each appears in errors[], except where the table says otherwise.
List Records output uses object access
Code: LIST_RECORDS_OBJECT_ACCESS (warning)
A list-records step returns an array of records, even when one record matches. If a later step reads a field with object-style access ({{customers.title}}) instead of indexing the array first ({{customers.0.metadata.title}}), the reference resolves to undefined at runtime.
The validation message names the variable and field, and gives the fix:
A get-record step returns one object, so object-style access works and this warning does not fire. A deprecated retrieve-record step is checked according to the step type that its configuration resolves to. For more information, see Migrate from retrieve-record and Using record steps (get/list/upsert).
Record field read outside metadata
Code: RECORD_FIELD_PATH (warning)
A get-record step returns one record object, and a list-records step returns an array of them. A record has id, name, type, metadata, createdAt, updatedAt, ownerId, and schemaValid at the top level. The fields that you write live under metadata. A reference such as {{lead.score}} resolves to undefined at runtime.
The warning names the field and gives the corrected path, for example {{lead.metadata.score}} or {{leads.0.metadata.score}}. The warning does not block a save.
Deprecated retrieve-record step type
Code: DEPRECATED_STEP_TYPE (warning)
The retrieve-record step is deprecated. Replace it with Get Record and List Records. Runtype resolves an ID-based lookup as get-record and a type, name, or filter lookup as list-records at execution time.
This warning reminds you to migrate the step when you next edit the Flow. The warning does not block a save. The step continues to run with its resolved type.
The warning message names the replacement step:
Upsert-record source variable is not a JSON object
Code: UPSERT_RECORD_SOURCE_NOT_JSON (warning)
An upsert-record step’s sourceVariable must reference a JSON object because the record metadata field accepts a JSON object. A common mistake is to send plain-text output from a prompt step, with a responseFormat other than json, directly to upsert-record without a contentField. That output is a string, so the step fails at runtime.
The validation message names the prompt step and gives three fixes:
Upsert-record source variable is a template
Code: UPSERT_SOURCE_TEMPLATE (warning)
An upsert-record step’s sourceVariable is a variable name, such as classification. The step does not substitute templates in this field, so a value such as {{classification}} is looked up literally and the run fails with Source variable '{{classification}}' not found in context. Use the bare name. The warning’s details.suggestion gives it.
Fetch step failure feeds a later step
Code: FETCH_CLASS_SWALLOWING_FEED (warning)
A fetch-url, api-call, crawl, or search step has no errorHandling setting, and a later transform-data or upsert-record step reads its output. With errorHandling unset, a failed request continues with an empty result. The later step then runs over zero data as if the API returned nothing, and can write empty or wrong Records.
The warning message names both steps and the fix:
Set errorHandling.onError to fail on the fetch step when later steps depend on its data. If an empty result is expected, set errorHandling.onError to continue explicitly, and the warning stops. For more information, see Using fetch-url and api-call steps.
Transform-data language does not match the sandbox provider
Code: TRANSFORM_LANGUAGE_PROVIDER_MISMATCH (warning)
A transform-data step triggers this warning when its language is not javascript, including python and typescript, and its sandboxProvider is a JavaScript-only environment: Cloudflare Worker or QuickJS. The warning also applies when sandboxProvider is unset, because the default is Cloudflare Worker.
No environment can run that pair. The step can treat the source as JavaScript or fail to produce output. Because transform-data continues on error by default, you might see an empty result instead of a visible failure.
The validation message names the incompatible settings and the fix:
To fix the mismatch, switch the execution environment to Runtype Sandbox or Daytona Sandbox, or set language to javascript. Both Runtype Sandbox and Daytona Sandbox run JavaScript, TypeScript, and Python. In the flow editor, selecting a JavaScript-only environment clears the language field.
This warning does not block a save. Flows saved before this check can still contain the mismatched pair.
Transform-data script calls fetch with network access off
Code: SANDBOX_NETWORK_ACCESS_DISABLED (warning)
A transform-data script calls fetch(), but the step’s network access is off. The execution environment blocks outbound requests while network access is off, so the request fails at runtime.
The warning also applies to QuickJS (Legacy), which can’t make outbound requests. On Cloudflare Worker (Default) or Runtype Sandbox, set Network Access to Unrestricted, or to Allowed Hosts Only with the host in Allowed Hostnames. In a Flow definition, set networkAccess to "on" or to an allowedHostnames list that includes the host. You can also switch to Daytona Sandbox, which doesn’t use this setting. For more information, see Control network access.
Transform-data tools on an environment that cannot call them
Code: TRANSFORM_POOL_TOOLS_ON_CONTAINER_PROVIDER (warning)
A transform-data step attaches tools through config.tools, but its execution environment is not Cloudflare Worker. Only Cloudflare Worker lets a script call attached tools. On other environments, every call to one of those tools fails at runtime, and because transform-data continues on error by default, the step can report success with an empty output.
Switch the step to Cloudflare Worker, or remove the tools.
Transform-data script issues
Codes: CODE_-prefixed codes and MISSING_LANGUAGE
Runtype also checks the script of a transform-data step when you save it. Each issue that the script check finds appears with a CODE_ prefix, such as CODE_SYNTAX_ERROR or CODE_SHADOWED_FLOW_VARIABLE, in errors[] or warnings[] depending on its severity. details.line and details.column locate the problem in the script, and the message repeats them.
Two common script errors are:
CODE_SYNTAX_ERROR: the script does not parse. Fix the syntax at the reported line and column.CODE_SHADOWED_FLOW_VARIABLE: the script declares a variable with the same name as a Flow variable. Runtype makes each Flow variable available to the script under its own name, so the redeclaration fails when the script loads. Rename your variable, or read the value throughinput.
MISSING_LANGUAGE is a warning for a step that uses Runtype Sandbox without a language. The step runs as JavaScript. Set language to javascript, typescript, or python to make the choice explicit.
For more information, see Using transform-data steps.
Store-vector source is unresolved
Code: STORE_VECTOR_SOURCE_UNRESOLVED (warning)
A store-vector step resolves vectorsSource by looking up a variable. The variable must come from an earlier step or a Flow input. If neither source defines it, dispatch fails with a field-specific error explaining that vectorsSource did not resolve to an embedding.
The validation message names the missing variable and a typical source:
To fix the warning, set an earlier step’s outputVariable to embeddings, or declare embeddings as a Flow input. A generate-embedding step commonly produces this value.
Store-vector source does not carry the embedding model
Codes: STORE_VECTOR_MODEL_MISSING (error) and STORE_VECTOR_SOURCE_MODELLESS (recommendation)
Set vectorsSource to the whole generate-embedding output, such as emb, not emb.embedding, so the embedding model and dimensions travel with the vector. A store-vector step has no embeddingModel setting of its own. A pgvector store needs the model name, because vector search finds a row only when the row’s model matches, so a store whose source carries no model fails at execution time instead of saving the row.
Validation reports two source shapes that drop the model:
- A
.embeddingpath into agenerate-embeddingoutput, such asemb.embedding. This path returns only the vector, so the step always fails. Validation reportsSTORE_VECTOR_MODEL_MISSINGas a blocking error. - A transform-data output. Your script decides the shape of this output and might keep the model, so validation reports
STORE_VECTOR_SOURCE_MODELLESSas a recommendation.
For a dot-path source, the error message has this form:
To fix a transform-data source, spread the whole generate-embedding output in the script, such as { ...embeddingOutput, ...extras }, so model and dimensions stay in the result. Don’t fill in model and dimensions with unrelated values, such as the name of the text model. The store accepts them and overwrites a correct model name.
The following rules also apply:
- Vectors from other sources. For a vector that
generate-embeddingdid not produce, supply an object with anembeddingarray, amodelstring, and a numericdimensions. - Batch stores. Each batch item needs its own non-empty
embedding_model. A batch-modegenerate-embeddingstep adds it automatically. When a batch store reads its items from a transform-data output, validation reportsSTORE_VECTOR_SOURCE_MODELLESSand names the required per-item key. - Error handling. With
errorHandlingunset, a store without a model reports a failure and writes nothing. An explicitcontinuesubstitutes the step’sdefaultValue, or the step’s standard error result (destination,storedCount: 0,error) when no default is set. An explicitfailstops the Flow. In a batch store, an item without anembedding_modelfails withNo embedding model, and the valid items still store. - Other destinations. These checks apply only to stores that target
pgvector. Weaviate and Vectorize accept a bare vector.
A reference to the whole generate-embedding output doesn’t trigger either code. If no earlier step produces that variable, Runtype reports STORE_VECTOR_SOURCE_UNRESOLVED instead.
Tool call strategy required on a multi-step prompt
Code: TOOL_STRATEGY_REQUIRED_MULTISTEP (warning)
Setting toolCallStrategy to required on a prompt step forces a tool call on every step. Unless the turn is limited to one tool call, the model cannot return a final text answer, so the output is empty. This warning fires when maxToolCalls is unset, set to a value greater than 1, or the prompt runs in a multi-turn loop.
The validation message describes the supported configurations:
Set toolCallStrategy to auto so the model can finish with text. Keep required only when you limit the turn to one forced tool call with maxToolCalls set to 1 and no loop. For more information, see Using prompt steps.
Tool intent policy on a flow prompt step
Code: TOOL_INTENT_POLICY_AGENT_ONLY (warning)
A prompt step sets tools.intent. Only an Agent’s durable run records tool intents, so a flow prompt step ignores the setting.
The validation message explains the scope:
Remove tools.intent from the prompt step, or set it on the Agent that calls the tool. For more information, see Require a committed intent before a tool runs.
Tool call strategy none with tools attached
Code: TOOL_STRATEGY_NONE_WITH_TOOLS (warning)
Setting toolCallStrategy to none forbids the model from calling a tool. When the same prompt step also attaches tools through toolIds, runtimeTools, mcpServers, a subagent pool, or a code-mode pool, none of those tools can ever run. They are dropped from the request, and the step answers as if no tools existed.
The validation message names the attached sources:
Set toolCallStrategy to auto so the model can call the tools, or remove the attached tools when the step should answer from the prompt alone.
Provider-native tools from more than one provider
Code: PROVIDER_TOOLS_MIXED_OWNERS (warning)
Some catalog tools are executed by the model’s own provider rather than by Runtype: builtin:anthropic_web_search and builtin:anthropic_web_fetch (Anthropic), builtin:openai_web_search (OpenAI), builtin:xai_web_search and builtin:xai_x_search (xAI), and builtin:google_search (Google). A prompt step that attaches such tools is routed to the provider that owns them. One request reaches one provider. When a step attaches provider-native tools from two providers, only one provider’s tools run, and the other provider’s tools are dropped or sent as inert function tools with no error.
Runtype routes the step to the first provider whose tools need a direct connection to that provider. Anthropic, OpenAI, and xAI tools need a direct connection, and builtin:google_search also works through the Vercel AI Gateway. When every attached provider works through the gateway, the first provider-native tool in toolIds decides the route.
This warning fires when the provider-native tools on a single top-level prompt step belong to more than one provider. Managed tools such as builtin:exa never count, and neither do IDs the catalog does not know. details.winner names the provider the step is routed to, details.owners lists every provider, and details.toolIdsByOwner groups the attached IDs by provider. The same check runs on an Agent’s tool IDs.
The validation message names the provider that runs and the tools that go inert:
Keep the provider-native tools of one provider on the step, or move the other provider’s tools to a separate prompt step whose model that provider serves.
Optional parameter interpolated into an external tool template
Code: OPTIONAL_PARAM_IN_TOOL_TEMPLATE (warning)
An external runtime tool interpolates {{parameter}} into its URL, a header value, or its request body. Runtype substitutes those templates from the arguments the model supplies. An argument the model leaves out stays in the request as its literal {{parameter}} text, so the outbound call is malformed and the third-party service usually answers with a confusing 4xx error.
This warning fires when the templated name is declared in parametersSchema.properties, is absent from parametersSchema.required, and the template has no fallback. Runtype reports one warning per tool and parameter, and details.locations lists where the parameter appears (url, headers, body).
The validation message names the tool and the parameter:
Add the parameter to parametersSchema.required when the request cannot work without it. When it is optional, give the template a fallback, such as {{units ?? 'metric'}}, so a missing value renders as a real value. Parameters that are not declared in parametersSchema.properties, {{secret:NAME}} references, and templates that already carry a fallback are not reported.
String tool parameter that carries embedded JSON
Code: TOOL_PARAM_EMBEDDED_JSON_STRING (recommendation)
A runtime tool’s parametersSchema declares a property that admits a string (type: "string", a nullable type: ["string", "null"], or an anyOf / oneOf with a string member) whose name or description says the model must write a JSON document inside the string. Names such as config, payload, json, or any name ending in json count, and so does a description that mentions a JSON string, stringified JSON, or JSON-encoded data. Models drop escapes when they nest JSON inside a JSON string. At runtime the call arrives as malformed JSON, Runtype rejects it as an invalid tool input, and the turn is wasted. Runtype repairs syntax-only defects such as trailing commas, unbalanced brackets, and unescaped inner quotes when it can, and logs each repair, but the schema is the durable fix.
This recommendation identifies a non-blocking design issue. The Flow runs as before. details.parameter names the property and details.signal reports whether the name or the description suggested embedded JSON.
The recommendation message names the tool and the parameter:
Declare the property as { "type": "object" } with its own properties so the model emits structured JSON, and stringify it inside the flow or tool wherever the string form is needed. Properties of any other type, and string properties whose name and description do not mention JSON, are not reported.
This recommendation is not limited to Flow validation. Saving a tool through POST /v1/tools or PUT /v1/tools/{id} runs the same check over the tool’s own parametersSchema, and a flagged property comes back on the saved tool as validation.recommendations[] with the path parametersSchema.properties.<name>. The save always succeeds.
Conditional compares an unquoted template against a string
Code: CONDITION_UNQUOTED_TEMPLATE_COMPARISON (warning)
A condition or when predicate uses JavaScript after template substitution. If you compare an unquoted {{...}} placeholder with a string literal, such as {{analysis.health}} === 'watch', a non-numeric value becomes a bare identifier. The expression (healthy === 'watch') then throws a ReferenceError at runtime.
The validation message shows the expression and the fix:
For a string comparison, quote the placeholder: '{{analysis.health}}' === 'watch'. For a numeric value, compare the value as a number. For more information, see Using conditional steps.
System prompt embeds a per-run variable
Code: CACHE_VOLATILE_SYSTEM_PROMPT (recommendation)
A prompt step’s system prompt embeds a per-run temporal variable such as {{_now}}, {{_execution.*}}, or {{_schedule.*}}. The system prompt changes on every execution, so automatic prompt caching in Runtype cannot reuse the cached prefix across requests. This recommendation identifies a non-blocking optimization issue. The Flow runs the same way, but the cache has fewer reusable prefixes.
The recommendation message names the variable and the fix:
Move the changing value into the user prompt or a later step so the system prompt stays identical across runs. For more information, see Prompt caching.
Prompt input is only an upstream prompt output
Code: PROMPT_INPUT_UNGUARDED_UPSTREAM_OUTPUT (recommendation)
A prompt step’s whole user prompt is one {{var}} reference to an earlier prompt step’s output, with no fallback. A prompt step that fails or stalls writes an empty string under the default continue behavior, so this step would have no input left. Runtype fails the step before it reaches the model, with an error naming the empty variable, so the run reports a failure one step away from the cause.
The recommendation message names the upstream step and the fix:
A step that also attaches a conversation history with previousMessages fails differently, and its message says so: the emptied reference contributes nothing, the step still runs on the conversation history alone with the intended input missing, and it fails before it reaches the model only when that history is empty. The details.carriesConversationHistory field reports which outcome applies.
Give the reference a fallback, add instructions around it, or set errorHandling to fail on the upstream step. A reference that already carries a fallback, sits beside other text, or points at a step that is not a prompt step is not reported.
Template references a reserved identity field
Code: RESERVED_IDENTITY_VARIABLE (recommendation)
A template references {{_tenant.projectedId}} or {{_endUser.projectedId}}. Runtype creates a durable projection ID (tnt_* or eu_*) for a resolved tenant or end user and uses it to scope Records, Secrets, and approval grants. Runtype keeps this ID out of the identity that prompts receive, so the reference resolves to an empty value instead of the ID. This recommendation does not block a save or run.
projectedId is a reserved key on both _tenant and _endUser. If you supply this key in the dispatch request, Runtype drops it instead of passing it through. Other fields remain addressable.
The recommendation message names the reference and the replacement:
Use {{_tenant.id}} or {{_endUser.id}}, the identity that you asserted on the request, when a template needs to address the tenant or end user. For more information, see Flow variables and templates.
Memory is shared across every version and alias
Code: MEMORY_SHARED_ACROSS_POINTERS (recommendation)
An Agent enables memory but nothing keys its memory bucket on the release pointer, so every version, every release alias, and every request pinned to a versionId reads and writes the same memory. This is the default, and it is deliberate: memory belongs to the subject, not to the configuration version that answered. The Agent runs as written.
Act on this recommendation when a preview alias starts writing memory that live traffic must not see. Append the pointer to memory.profileTemplate in one of these ways:
{{_endUser.id}}/{{_agent.alias || "live"}}gives each preview alias its own bucket and keepsliveon the shared one. The|| "live"fallback matters:{{_agent.alias}}is absent on a request with no selector and on a request pinned to a bareversionId, and an unresolved variable turns memory off for that execution.{{_endUser.id}}/{{_agent.versionId}}starts every version empty, for setups where a version’s behavior must be reproducible.
When the Agent sets a tenancyStrategy, Runtype computes the namespace and ignores profileTemplate, so set memory.pointerScope to "alias" or "version" instead. The recommendation stops when the setting that applies names the pointer: the template on an ordinary Agent, or pointerScope on an Agent with a Tenancy Strategy. Setting pointerScope on an Agent without one changes nothing and reports MEMORY_POINTER_SCOPE_IGNORED instead. For the full set of options, including what happens on rollback and when you retarget an alias, see Memory namespaces and release pointers.
Pointer-keyed memory template has no fallback
Code: MEMORY_POINTER_TEMPLATE_UNGUARDED (recommendation)
An Agent’s memory.profileTemplate names {{_agent.alias}} or {{_agent.versionId}} with no fallback. Those variables resolve only when the request selects a version, and Runtype turns memory off for an execution whose template does not resolve. An Agent whose template is meant to isolate previews therefore runs with memory off on every request that names no version.
Give the reference a fallback so requests without a selector use a real bucket:
The same code fires with details.defect: "label-only" when the template keys the bucket on {{_agent.version}} and nothing else. That variable is the version’s human-readable label: free text, not unique across versions, and absent on an unlabeled version. Key on {{_agent.versionId}} and keep the label for display.
Memory pointer scope has no effect
Code: MEMORY_POINTER_SCOPE_IGNORED (warning)
An Agent sets memory.pointerScope to "alias" or "version", but has no tenancyStrategy. Runtype adds a pointer segment to the memory namespace only for an Agent with a Tenancy Strategy. Every other Agent resolves memory.profileTemplate, which never reads pointerScope, so an Agent configured for per-pointer isolation keeps one shared bucket.
Fix it in the direction you intended:
- To isolate by pointer without a Tenancy Strategy, key the template instead:
{{_endUser.id}}/{{_agent.alias || "live"}}for preview isolation, or{{_endUser.id}}/{{_agent.versionId}}for per-version buckets. - To have Runtype compute the namespace, set a
tenancyStrategyon the Agent.pointerScopethen takes effect. - If you intended one shared bucket, remove
pointerScope.
For the full set of options, see Memory namespaces and release pointers.
Runtime tool claims a reserved tool name
Code: RESERVED_TOOL_NAME (error, blocks the save)
A runtime tool’s name collides with runtype_set_state, the name Runtype reserves for the Agent state write tool. An Agent with state is told to call that name, so a tool answering to it would receive whatever the Agent writes to state.
Tool names are lowercased and every non-alphanumeric character becomes _ before the model sees them, so runtype-set-state, RUNTYPE_SET_STATE, and runtype.set.state are reserved alongside the exact spelling. runtype_ is a reserved tool namespace in general.
The error message names the tool and the reserved name:
Rename the tool to anything outside the runtype_ namespace. This check also runs offline, so the SDK and the dashboard report it before you save. For more information, see Agent tools.
Custom tool egress settings are malformed
Code: CUSTOM_TOOL_EGRESS_CONFIG_INVALID (error, blocks the save)
A custom runtime tool’s networkAccess or secrets entry can’t be used as written. For example, networkAccess isn’t 'off', 'on', or a non-empty allowedHostnames list, a secret key isn’t a bare managed secret name, the same secret appears twice, the Tool declares more than 10 secrets, or a hostnamePattern isn’t one public host. *, *.com, public suffixes such as *.co.uk or *.github.io, IP addresses, localhost, and .internal names are refused.
Fix the field that the error path names. Write the secret key as GITHUB_TOKEN, not {{secret:GITHUB_TOKEN}}. For the fields, see Call an API from custom code.
Custom tool asks for egress its environment can’t provide
Code: CUSTOM_TOOL_EGRESS_PROVIDER_UNSUPPORTED (error, blocks the save)
A custom runtime tool sets networkAccess or secrets on an execution environment that can’t honor it. QuickJS has no network access and no egress proxy. Daytona Sandbox manages its own egress, so it can’t enforce an allowedHostnames list or inject secrets.
Set sandboxProvider to cloudflare-worker or runtype-sandbox, or remove the egress settings.
Custom tool secret goes to a host its network access blocks
Code: CUSTOM_TOOL_SECRET_HOST_NOT_ALLOWED (error, blocks the save)
A custom runtime tool declares a secret for a host that its own networkAccess doesn’t allow. The request that carries the secret would be blocked, so the secret could never be used. An omitted networkAccess means 'off', so it allows no host.
Add the secret’s hostnamePattern to networkAccess.allowedHostnames.
Flow tool writes into a reserved system scope
Code: FLOW_TOOL_OUTPUT_VARIABLE_RESERVED (error, blocks the save)
A flow-type runtime tool sets config.outputVariable to a name starting with _. Names in that namespace are Runtype’s system scopes (_flow, _user, _execution, _record), and they carry caller identity into every template and step in the run. A flow tool writing its result there would overwrite that identity.
The error message names the variable:
Pick a name outside the _ namespace, such as enrichment, or clear the field to keep the tool’s default result envelope. The same rule applies to a flow tool attached to an Agent. This check also runs offline, so the SDK and the dashboard report it before you save. For more information, see Flow variables and templates.
Tool-call step points at an unknown catalog tool
Code: TOOL_CALL_STEP_UNKNOWN_CATALOG_TOOL (error, blocks the save)
A tool-call step’s config.toolId starts with builtin: or platform:, but no catalog tool answers to that ID. A known catalog ID is valid: a tool-call step invokes built-in and platform tools directly, alongside a saved tool’s tool_... ID or its tool:<name> reference.
The error message names the tool and how to fix the reference:
Fix the ID against the tool catalog (list_tools in MCP, or GET /v1/tools). For more information, see Built-in tools.
A tool-call step runs at a fixed point in the Flow with the parameters that you wrote, and no model chooses it. It therefore has no approval gate, for a catalog tool exactly as for a saved tool. A step on builtin:send_email or a builtin:browser:* tool runs unattended every time the Flow reaches it. Approval gating applies to model-chosen tool calls in an Agent or a prompt step.
Tool-call step ignores errorHandling
Code: TOOL_CALL_ERROR_HANDLING_IGNORED (warning)
A tool-call step sets config.errorHandling.onError. Tool-call steps read only their own config.onError field (fail, continue, or retry), so errorHandling.onError has no effect. A failed tool call is handled as config.onError. When onError is unset, the value is fail, and a failed tool call fails the Flow execution. details.effectiveOnError names the value that applies.
Move the value to config.onError and remove errorHandling. fallback has no tool-call equivalent: set config.onError to continue and branch on the { error, result: null } output instead.
Extract step field map is invalid
Code: EXTRACT_FIELDS_INVALID (error, blocks the save)
An extract step’s config.fields breaks a field rule: a name the model refuses, an unknown type, an option the type does not take (for example decimals on a text field), or a required option missing (items on a list; min and max on an estimate; min, max, horizon, and interval on a forecast). One issue is reported for each broken rule at config.fields.<name>, and details.code names the rule.
Fix the named field. See Extraction for every type and its options.
Extract step names a model that is not an extraction model
Code: EXTRACT_MODEL_UNKNOWN (error, blocks the save)
An extract step’s config.model is not in the extraction model catalog. A chat model or a decision model cannot run the step.
Omit model to use the default (@cf/cloudflare/clairic), or pick an ID from GET /v1/extraction-models.
Extract step lists too many images
Code: EXTRACT_IMAGES_TOO_MANY (error, blocks the save)
An extract step lists more config.images than the model reads. details.maxImages holds the limit (4 for Clairic).
Keep at most that many images, or split the pages across two steps.
Extract step has nothing to read
Code: EXTRACT_STATE_EMPTY (warning)
An extract step’s config.state is empty and no images are configured. At run time, an empty resolved text with no images is an input-contract failure.
Put the text to read in state, for example {{message}}, or add at least one image.
Extract field says nothing to the model
Code: EXTRACT_FIELD_UNDESCRIBED (recommendation)
A field has a one- or two-character name and no instructions. The field name is what the model sees, so such a field asks for nothing in particular.
Use a descriptive name such as order_id, or add one sentence of instructions.
Tool configuration references an unresolved question set
Code: QUESTION_SET_UNRESOLVED (error, blocks the save)
The judgment tool’s config.toolConfig.questionSetId (a tool-call step) or the matching entry of config.tools.toolConfigs (a prompt step) names a question set that cannot be resolved at save time. The check uses the same lookup that runs at execution time, so a reference that fails here would also fail the run: a judgment call never falls back to inline questions. This check runs only for authenticated validation.
One of three reasons produces the error, each with its own message:
Fix the reference:
- Use
list_question_sets(MCP) orGET /v1/question-setsto find a real question set ID for this account. - Call
publish_question_set_version(MCP) orPOST /v1/question-sets/{id}/versions/{version}/publishif the set exists but has no published version, or if the specific version you named is a draft. - Omit
versionto resolve the current published version instead of pinning to one that might not exist.
Execute-agent step uses a template variable for the Agent
Code: EXECUTE_AGENT_TEMPLATED_REF_UNRUNNABLE (warning)
An execute-agent step’s config.agentId is a template expression such as {{agentVar}} instead of a fixed Agent ID. Runtype can’t check an Agent that it only learns about at run time, so every surface refuses the whole Flow with 422 RUNTIME_LANE_INELIGIBLE (reason ineligible:unresolvable-agent-ref) before any step runs.
The warning does not block the save, so an existing Flow keeps its definition. The Flow does not run until you replace the reference.
The warning message names the reference and the fix:
To pick between Agents at run time, put a conditional step ahead of the call and give each branch its own execute-agent step with a fixed Agent ID. Runtype checks each of those references when you save, so the approval, client-tool, and sandbox checks still apply. The Flow-level RUNTIME_LANE_INELIGIBLE recommendation does not report a templated reference. This warning reports it instead.
An execute-agent step names an agent whose live alias is archived
Code: EXECUTE_AGENT_TARGET_NOT_DEPLOYED (warning)
An execute-agent step names an Agent in your organization that has archived its live release alias. A run that names no version selector can resolve the live alias, and then this step fails with AGENT_NOT_DEPLOYED partway through the Flow.
Deploy a version to the target Agent’s live alias and the warning goes away.
An Agent that was never published does not report this. Its first run without a selector deploys its current configuration as version 1, so the step runs. Agents owned by a person rather than an organization, and Agents of type external or claude_managed, keep running their current configuration and never report it either.
Record step writes an undeclared collection field
Code: RECORD_FIELD_NOT_IN_SCHEMA (warning)
When an upsert-record or update-record step targets a schematized collection, Runtype checks the fields that the step writes against the collection schema. If the schema does not declare a field and sets additionalProperties to false, Runtype flags the write in warn mode. The runtime rejects it in enforce mode. Runtype reports this warning at save time.
Runtype checks only fields that it can read without running the Flow: the keys in an update-record step’s updates map and the contentField of an upsert-record step. The warning is independent of the collection’s validationMode because the schema defines the collection’s data model in either mode. An open additionalProperties setting accepts extra fields, so the warning does not fire.
The validation message names the field, collection, and schema version:
Add the field to the collection schema, or remove the field from the step’s updates or contentField. For more information, see Defining a schema.
Record step leaves a required schema field unmapped
Code: RECORD_REQUIRED_FIELD_UNMAPPED (recommendation)
When an update-record step uses mergeStrategy: "replace", the step replaces the record’s entire metadata value. Runtype therefore checks whether the step sets every field that the target collection schema marks required. If the step does not map a required field, Runtype reports this recommendation before the replaced record fails schema validation.
This recommendation applies only to complete replace mappings and is independent of the collection’s validationMode. A merge or deep-merge write can inherit a required field from the existing record, so it does not trigger this recommendation.
The recommendation message names the required field, collection, and schema version:
Add the required field to updates, or remove it from the schema’s required list.
Durable pause exceeds its limits
Codes: WAIT_UNTIL_MAX_ATTEMPTS_CLAMPED (warning), WAIT_UNTIL_POLL_EXCEEDS_RESUME_TTL (error), and CRAWL_COMPLETION_TIMEOUT_EXCEEDS_RESUME_TTL (error)
A wait-until step or an asynchronous crawl step pauses the Flow while it waits. Validation checks that the pause fits the platform limits:
WAIT_UNTIL_MAX_ATTEMPTS_CLAMPED:poll.maxAttemptsis greater than 1000. A durable poll runs at most 1000 attempts, so Runtype lowers the count to 1000. To cover the same time window, raisepoll.intervalMsinstead of adding attempts.WAIT_UNTIL_POLL_EXCEEDS_RESUME_TTL: the poll’s worst-case duration (attempts multiplied by the per-attempt timeout, plus the interval between attempts) is longer than the 30-day limit for a paused Flow, less a one-hour reserve. A poll that outlives that limit can never resume, so the Flow would stop with no error and no output. Reducepoll.maxAttempts,poll.intervalMs, orpoll.http.timeout, or split the wait across separate runs, such as a scheduled Flow that checks again.CRAWL_COMPLETION_TIMEOUT_EXCEEDS_RESUME_TTL: the crawl’s completion timeout, including time spent on requests, exceeds the same limit. Runtype refuses such a crawl only after it has been submitted to the crawl provider, so fixing it at save time avoids paying for a crawl that can’t finish. Reduce the values that the message names, such ascompletionTimeoutMsorpollIntervalMs.
Each message states the computed duration and the values that drive it. For more information, see Timeouts and long-running work.
Flow uses a capability that cannot run
Code: RUNTIME_LANE_INELIGIBLE (recommendation)
Some Flow configurations can’t run on any surface. When you dispatch such a Flow, Runtype refuses it before any step runs: interactive dispatch returns 422 with the code RUNTIME_LANE_INELIGIBLE, and batch, eval, and schedule runs are refused the same way. This recommendation reports those configurations when you save, and names each one with a stable ineligible:* reason in details.reasons. It does not block the save.
The following configurations can’t run:
- A top-level
wait-untilorcrawlstep on a surface that can’t pause a Flow and resume it later. Interactive dispatch can run these steps, and so can a Flow where the step sits inside a top-level conditional. Because save-time validation can’t know which surface you’ll use, the recommendation names the step on every save. - A custom tool configured for a container-backed sandbox, a language other than JavaScript, or the legacy code-mode setting. Custom tools run only JavaScript on Cloudflare Worker or QuickJS.
- A transform-data step whose language and execution environment don’t match. The language and provider mismatch warning reports the same pairing. Python or TypeScript on Runtype Sandbox or Daytona Sandbox runs normally.
- A vector step that Runtype can’t represent, such as a
store-vectorstep with no vector ID source or a Weaviate destination. A non-default Vectorize index selected withvectorizeConfigIdruns normally. - An unrecognized step type.
A wait-until pause can also fail after it starts. A human approval gate, per-dispatch client tools, or a browser-executed tool ahead of the pause doesn’t prevent the pause, but the run can’t resume without an attached client to answer. The resumed run fails with a message that explains the cause. To complete such a run, dispatch it again with a client attached, or remove the configuration that needs one.
The message names the unsupported capabilities, and details.reasons contains the stable ineligible:* codes:
To run the Flow on every surface, remove or rewrite the flagged configuration. For example, configure a custom tool with the Cloudflare Worker or QuickJS sandbox and JavaScript.
On the Flow validation response, this recommendation’s path is flowSteps. Product and full product object (FPO) validation also reports it for an inline capability Flow, with path set to capabilities[N].flow. Runtype doesn’t re-check a capability that references an existing Flow or Agent by ID. That Flow or Agent receives the recommendation when you save it.
A human approval gate doesn’t by itself cause this recommendation. The surface decides whether an approval can be answered, so the approval enforcement warning covers it instead.
For authenticated validation, the check also includes the Agents that execute-agent steps reference. The recommendation fires when a referenced Agent has a configuration that can’t run, such as a custom tool that needs a container-backed sandbox. A reference to an Agent that doesn’t exist is reported as AGENT_NOT_FOUND instead, and a templated reference as EXECUTE_AGENT_TEMPLATED_REF_UNRUNNABLE. Unauthenticated validation reads only the Flow’s own steps.
Agent configuration that cannot run on some surfaces
Code: RUNTIME_AGENT_LANE_INELIGIBLE (Agent save advisory in _warnings)
When you save an Agent whose configuration can’t run on some or all surfaces, the save response includes this advisory in _warnings. The save succeeds, but a dispatch on an unsupported surface is refused. Claude Managed Agents and external Agents in Delegate mode run on their own execution paths, so this advisory doesn’t apply to them. An external Agent with skillOrchestration: "managed" does receive it. On a surface that can’t run its managed definition, execution fails instead of delegating directly to the external Agent.
An Agent can’t run on any surface when its configuration includes either of the following:
- A
wait-untilorcrawlstep anywhere in the Agent’s inline Flow tools, top-level or inside a conditional. An Agent turn never pauses durably, so every surface refuses the Agent withineligible:wait-untilorineligible:crawl. Put the long wait in a plain Flow instead, and dispatch that Flow on a surface that supports durable pauses. For more information, see Timeouts and long-running work. - A configuration that also makes a Flow unrunnable, such as a custom tool that needs a container-backed sandbox.
The logging policy setting config.loggingPolicy: "off", config.piiRedaction: "redact", and config.temporal never produce this advisory. Every surface that dispatches an Agent applies all three. That includes an execute-agent Flow step and a subagent. They have no previous turn, so groundNow adds the current time and injectElapsed adds nothing. Only the self-hosted runtime’s detached subagent tools still reject a temporal block.
Several settings run on some surfaces and are refused on others. For these, the advisory message names the surfaces instead of telling you to remove the setting. The following table lists them:
The surfaces that refuse memory and tenancy don’t provide the memory or tenancy information that the setting needs.
Client-executed tools (local and webmcp) never cause a refusal. A surface without a live caller removes the tool before the turn, so one Agent can carry a widget tool for its chat surface and still run its nightly schedule without that tool. Each run that removes a tool records the tool name in its log. Saving an Agent that gains such a tool while it’s bound to those surfaces returns a CLIENT_TOOL_UNRUNNABLE_ON_SURFACE notice that names them. If the model calls a client tool on a path where one remains, the run fails.
The advisory names the configuration paths and ineligible:* codes from the saved configuration only. It doesn’t analyze a capability that Runtype resolves when you save the Agent instead of storing it in the configuration. The advisory message has this form:
To fix it, remove or rewrite the flagged configuration path.
Retired capabilities
A retired capability is one that Runtype doesn’t run on any surface. The advisory for a retired capability says so explicitly. The only retired capability is config.tools.perToolLimits, which sets per-tool call limits on an Agent’s tools.
Interactive dispatch (POST /v1/dispatch and POST /v1/agents/:id/execute) and the product surfaces (chat, AG-UI, and the Product API) reject such an Agent with HTTP 400 and the code RUNTIME_AGENT_DEFINITION_FEATURE_UNSUPPORTED. Runtype checks for a retired capability first, so an Agent that has both a retired capability and another unsupported configuration is rejected for the retired one. Batch, evals, schedules, webhooks, messaging, and Product MCP fail the run. On A2A, Runtype applies this check only to a managed orchestrator Agent, which A2A rejects in the same way as interactive dispatch.
The advisory for a retired capability has this form:
To fix it, remove tools.perToolLimits from the Agent definition. Bound the loop with loopConfig.maxTurns or loopConfig.maxCost instead, or enforce the per-tool budget inside the tool itself.
Model advisory warnings
Runtype checks each prompt step’s model against the model catalog and against your account’s model settings. These checks describe the model selection rather than the Flow structure, so none of them blocks a save. They appear in warnings[], except NON_ROUTED_MODEL_VERSION, which appears in recommendations[].
A model the catalog does not know, such as a custom model you added yourself, is skipped by every check in this group.
Model is deprecated
Code: DEPRECATED_MODEL (warning)
The selected model is marked deprecated in the model catalog. A deprecated model still runs until its provider retires it, and it stops working without warning when that happens.
When the catalog knows a supported routed alias for the model’s family, the message names that alias as the replacement. When it does not, the message asks you to choose a supported model. Switch to the suggested alias, or pick a routed model such as claude-sonnet-5 or gpt-5.4-mini.
Model pins an explicit version
Code: NON_ROUTED_MODEL_VERSION (recommendation)
The step names a dated version or a provider-prefixed model ID instead of the routed alias for that family. A pinned ID keeps working, but it stays on one provider and one snapshot, so the step does not pick up the family’s later versions.
The recommendation names the alias:
Replace the pinned ID with the routed alias that the message names. Keep the pinned ID when you need one exact snapshot, for example to hold an Eval baseline steady.
A model ID whose only difference from its family is the version spelling, such as gemini-3.8-flash against the family gemini-3-8-flash, is not a pin. Both spellings name the same model and route identically, so this recommendation never appears for one.
This recommendation also stays quiet when the ID names a provider you added your own key for, such as an Azure or OpenRouter model. Adding the key is how you choose the provider, so Runtype does not suggest routing the step away from it. A dated version ID still gets the recommendation, because a provider key says which provider runs the step, not which snapshot of the model.
Model does not support a capability the step enables
Code: MODEL_CAPABILITY_MISMATCH (warning)
The step enables reasoning on a model that does not support reasoning, or enables artifacts on a model that does not support tool use. Artifacts are produced through injected emit tools, so a model without tool use cannot produce them.
The warning names the model and the capability:
Turn the capability off, or select a model whose catalog entry supports it. For more information, see Using prompt steps.
Model rejects the temperature parameter
Code: TEMPERATURE_UNSUPPORTED (warning)
The step sets temperature on a model that does not accept the parameter. Runtype drops the value before it sends the request, so the setting has no effect and the model samples at its own default.
The warning names the model:
Remove the temperature setting, or choose a model that supports it.
Output cap is below the reasoning floor
Code: MAX_TOKENS_BELOW_REASONING_FLOOR (warning)
The step sets maxTokens below 1024 while reasoning is active. maxTokens caps reasoning tokens along with the visible answer, so reasoning can consume the whole budget. The step then returns an empty response with stopReason: "length" and still reports success: true.
Reasoning counts as active in three cases, and details.reasoningSource says which one fired:
-
requested: you enabledreasoningon a reasoning-capable model. -
built-in: the model reasons by default, which covers the GPT-5 and o-series families. -
built-in-not-disableable: you disabledreasoning, but the model has no off switch, so it still reasons at a floor.details.disabledReasoningEffortnames that floor:- The o-series floors at
loweffort. - A Gemini model with a thinking-level ladder floors at the lowest level its catalog entry lists.
gemini-3.8-flashfloors atlow, and models that listminimalfloor atminimal. - A Gemini budget-mode model with no off toggle floors at its minimum thinking budget, reported as a string such as
"128-token thinking budget"(gemini-2.5-pro).
Gemini models with an off toggle, such as
gemini-2.5-flashandgemini-2.5-flash-lite, do turn thinking off, so disabling reasoning clears the warning on them. - The o-series floors at
The warning names the model, the cap, and the fix:
Raise maxTokens to at least 1024, or remove the cap. Disabling reasoning fixes the problem only on a model whose catalog entry has an off toggle. On other models, disabling reasoning lowers it to the floor that details.disabledReasoningEffort names.
Reasoning knob outside the model’s published range
Code: REASONING_KNOB_UNSUPPORTED (warning)
The step’s reasoning object sets a knob the model’s catalog entry does not accept. Each model publishes its own effort ladder and its own thinking-budget range, and a value outside them is either rejected by the provider or dropped before the request goes out. details carries { model, knob, requested, accepted }.
Three shapes trigger the warning:
- An effort level outside the model’s ladder. The check reads the field that Runtype sends to that provider:
reasoningEffortfor OpenAI and xAI,effortfor Claude models with adaptive thinking, andthinkingLevelfor Gemini 3 and later. budgetTokenson a Claude model with adaptive thinking. Adaptive thinking carries no token budget, so Runtype drops the value before it sends the request.- A thinking budget outside the model’s published minimum and maximum.
Each shape produces its own message:
Set the knob to a value the model publishes, or remove it. In the dashboard, reopening the model’s Reasoning controls and picking a level rewrites the setting for you, because the editor offers only the values that model accepts.
The check stays silent in four cases: the catalog makes no claim about the model’s knobs, reasoning is the boolean true form, which names no specific knob, reasoning.enabled is false, and the step is disabled. For more information, see Using prompt steps.
Model is not enabled for the account
Code: MODEL_NOT_ENABLED (warning)
The model is turned off in your account’s model settings, or it is not configured and no platform key or provider key can run it. The step fails at dispatch rather than at save time.
The warning names the model and the reason:
Enable the model in model settings, add a provider API key for its provider, or select a model your account can run. This check runs only for authenticated validation. For more information, see Managing AI models.
Approval gate is enforced on attended surfaces only
Code: APPROVAL_ENFORCED_ON_ATTENDED_SURFACES_ONLY (warning)
A prompt step sets config.tools.approval.require to ask a person to approve named tools before they run. Runtype enforces this gate only on surfaces that can present the prompt and resume the run: interactive dispatch (POST /v1/dispatch and the dashboard Run panel) and Agent execution.
Schedules, webhooks, batch, evals, messaging, MCP, and A2A can’t present an approval prompt. On these surfaces, Runtype refuses the run with ineligible:surface-approval-pause, and the gated tools don’t run without a decision.
Client Chat and chat widgets are a separate case. They can pause for a browser-side client tool and resume that visitor’s run through POST /v1/client/resume, but they can’t send an approval request to the Product owner and return the approved result to the visitor. When a Client Chat request can reach an approval-gated Flow or Agent, Runtype rejects it before execution with HTTP 501 and code APPROVAL_MODE_UNSUPPORTED.
The warning message names the split, and details carries both surface lists:
Runtype reports this warning only for a top-level prompt step with an effective require: true or a non-empty list of tool names. An approval block that sets only timeout, requestReason, or choices shapes the prompt but does not create an approval gate. It does not trigger the warning.
To fix this warning, keep the Flow on attended surfaces, or remove the approval requirement from the step if the Flow must run unattended. The approval-gated Agent warning identifies Agent bindings that cannot answer a gate.
Approval-gated Agent bound to a surface or mode that cannot answer
Code: APPROVAL_UNANSWERABLE_ON_SURFACE (warning)
An Agent that requires human approval before certain tools run needs a person who can answer the prompt. It also needs an execution mode that can resume afterward. If you bind that Agent to an unattended surface, or bind a single-pass Agent to Slack or Telegram, the approval-gated execution cannot continue. Runtype reports this warning while you edit the binding instead of at dispatch.
An Agent requires approval when it sets tools.approval.require, marks an individual tool as requiring approval, or binds a Skill whose tools expand the approval surface.
The following surface types have no approval-answering path: chat, webhook, schedule, email, discord, whatsapp, and generic messaging. Client Chat fails before execution with HTTP 501 and code APPROVAL_MODE_UNSUPPORTED instead of leaving an approval pending.
Slack and Telegram can show native Approve and Deny controls. SMS and iMessage have no buttons, so the person sends the decision in a reply. For more information, see Approvals on messaging surfaces. An ordinary Runtype Agent on any of these four surfaces needs a multi-turn Agent Loop run with a durable checkpoint to continue after the decision arrives. Runtype reports this warning for an approval-gated Runtype Agent when loopConfig.maxTurns is absent or set to 1. The warning stops after you enable Agent Loop with at least 2 turns. A Managed external Agent runs as an Agent Loop without an authored loopConfig. Telegram, SMS, and iMessage still require their documented inbound verification credentials.
Runtype reports this warning in three places:
- Product validation (
validate_productorPOST /validate) reports it inwarnings[], withpathpointing to the binding that triggered it, such assurfaces[2].routes[0].capabilityId. - Adding or updating a surface item through the REST endpoint or the
add_surface_itemMCP tool returns it inwarnings[]. - Saving an Agent returns it in
_warnings. This case fires only when you first add an approval requirement to an Agent that is already bound to unattended surfaces. The message names those surfaces.
The validation message names the Agent, surface, and surface type:
For a single-pass Slack, Telegram, SMS, or iMessage Agent, the message explains that the surface can resume approvals only for Agent Loop Agents.
This warning does not invalidate the binding: valid remains true and the save succeeds. One Agent can serve both a chat surface and a webhook surface in the same Product. Dispatch still determines whether each binding can run.
To fix the warning, remove the approval requirement from the Agent. For an ordinary Runtype Agent on Slack, Telegram, SMS, or iMessage, enable Agent Loop with at least 2 turns. A Managed external Agent already runs as an Agent Loop. Don’t move an approval-gated capability to Client Chat, because Client Chat rejects the request with APPROVAL_MODE_UNSUPPORTED.
Approval-gated Agent answered by reply text
Code: APPROVAL_REPLY_CAPTURE_ON_SURFACE (recommendation)
This recommendation covers an approval-gated Agent Loop Agent bound to an SMS or iMessage surface. The pairing works, but these transports have no approve or deny buttons, so the person sends the decision in a reply. An unclear reply prompts another request instead of authorizing an action.
Runtype reports a recommendation because the approval is answerable and the execution continues. The recommendation appears in product validation, when you add or update a surface item, and when you save an Agent. Keep approval-gated tools on this surface limited to low-risk actions. You can also bind the capability to a surface with explicit Approve and Deny controls, such as Slack or Telegram, or run it through the authenticated dashboard Run interface. For more information about reply keywords and expiry behavior, see Approvals on messaging surfaces.
Next steps
Continue with these related topics:
- Debugging flows: trace runtime errors after you dispatch a Flow.
- Flow variables and templates: fix variable reference warnings and pass data between steps.
- Using prompt steps: set the model, reasoning, and tool options a prompt step runs with.
- Using transform-data steps: choose an execution environment and network access for a script.
- Using record steps (get/list/upsert): review record step return shapes and source variables.
- Defining a schema: declare the collection fields that record steps write.
- Timeouts and long-running work: size durable pauses to fit their limits.
- Agent versioning and publishing: scope Agent memory to release pointers.
- Approvals on messaging surfaces: let people answer approval requests from Slack, Telegram, SMS, or iMessage.