reliant workflow validate runs the same rules, so a repository whose gate validates its workflows (reliant workflow validate --base origin/main) lints them too. An error fails the command; a warning is printed and does not. Lint never runs when a workflow is saved or started: a rule added in a later release cannot make an existing automation refuse to fire.
Allowing a finding
When you have judged a finding and it is fine, say so next to the node, with the reason:name:, triggers:, …) covers the whole workflow. Name several rules with commas. The reason is required, and a lint:allow that names an unknown rule or gives no reason is itself an error (lint-allow-invalid).
Rules
timeout-ignored
Warning.timeout: on a workflow or loop node bounds nothing. The runtime applies a node’s timeout to the activity that node dispatches (a command, a model call, an action), and a workflow or loop node dispatches none: its child’s nodes run on their own timeouts. A timeout: 45m on an agent node therefore reads as a bound and is not one. Bound the child’s own steps instead: timeout: on its run nodes, max_turns on an agent, iter.iteration in a loop’s while.
run-no-timeout
Warning. Arun node with no timeout: runs for the 30-day activity default, so a hung command (a prompt for input, a network call that never returns) looks like a slow one for a month. Set timeout: to the longest the command may take. In a workflow with concurrency.max_running this is slot-unbounded-wait, an error.
run-exit-code-unread
Warning. A non-zero exit does not fail arun node: it is an output, exit_code. If nothing reads it (an edge case, a condition, a template), a failed command routes exactly like a successful one. Branch on nodes.<id>.exit_code. A fixed sleep (a backoff) is exempt.
exit-code-unhandled
Error. An edge branches on arun node’s exit_code and has no default:. A script that can exit 4 (a conflict) when the cases name only 0 and 1 ends the run at that edge, silently, and the run reports success. Add a default: that reports the unexpected code.
edge-no-default
Warning. An edge withcases: and no default:: when no case matches, that branch of the run simply ends. Add a default:, even one that only saves a message saying nothing matched.
action-error-unread
Warning. Anaction node that fails (a 4xx, a revoked credential, a rate limit) does not fail the run: it sets is_error. If nothing reads nodes.<id>.is_error, the failure routes like a success. Branch on it, or allow the finding with the reason the failure does not matter (a best-effort label removal).
no-failure-path
Warning. A workflow that triggers start (an unattended run) has noon_failure: handler, so a run that fails tells nobody. Add one with a step that reaches a person: a PR comment, a message, an issue.
agent-turn-budget-default
Warning. An agent node (aworkflow or loop node whose child declares a max_turns input, such as builtin://autopilot-agent) does not set max_turns, so it runs on the child’s default. An agent that runs out of turns submits nothing. Set the budget the task needs, and say it in the prompt (“submit by turn 50”).
loop-unbounded
Warning. A loop with noitems: whose while: never reads iter.iteration has nothing that ends it except the condition itself. Add && iter.iteration < N.
agent-decode-free-text
Error. An expressionjson.decode()s (or parseJson()s) the free text of an agent: a call_llm node with no response_tool, or a ref’d agent given no response_schema. The model’s prose has no contract; the run breaks the first time it adds a sentence. Give the agent a response_schema (and response_tool_name) and read nodes.<id>.response.<field>.
slot-unbounded-wait
Error. A workflow withconcurrency.max_running holds a slot for as long as each run is open, paused runs included. Any step that can wait without bound holds that slot: a run node with no timeout:, a loop with no bound. (An approval or ask_question is not one: max_running bounds only trigger-launched runs, which are unattended, and an unattended run’s human gates resolve at once.) One stuck run then queues every later firing behind it. A workflow or loop node’s child holds the slot too, so lint reads the child (and every ref it reaches) and applies the same check to its steps; a timeout: on the node itself does not help (timeout-ignored). Give every such step a timeout (or a bound).
shared-checkout-fetch
Error when the step evidently runs in the project checkout; warning when it changes directory first and lint cannot tell. Agit fetch or git pull into the checkout the project’s chats and every other run share updates its remote-tracking refs and tags underneath them: two writers race (cannot lock ref … is at X but expected Y), and a tag that moved upstream fails every later fetch (would clobber existing tag). Fetch into this run’s private refs instead, which reads the shared objects without writing shared refs:
secret-literal
Error. A token, key or password is written into the definition: a GitHub, Slack, AWS or API key, a private key, a bearer token, or a secret-namedenv: set to a literal. Remove it, revoke it, and read it through a credential: credentials: inherit on a run node, an action’s connection:, or a hosted secret.
automation-only-untriggered
Warning.automation_only: true turns off starting the workflow from a chat. With no triggers: either, nothing will start it. Declare its triggers, or drop automation_only.
requires-undeclared-tool
Warning. A triggered workflow’srun command calls an external tool (gh, git, task, jq, forge, docker, kubectl, go, npm, …) that its requires.tools does not list. A run checks requires: on its machine before its first node and fails at once, naming everything missing; a tool it was not told about fails the step that calls it, midway, with earlier steps’ side effects already done. Declare it:
scenario-none
Warning. The workflow has no scenarios under.reliant/workflows/<name>/scenarios/, so none of its triggers or endings is tested. See Testing workflows.
scenario-trigger-uncovered
Warning. No scenario starts the workflow through this trigger. A scenario fires a trigger withtrigger:; the trigger’s filter must pass the payload and its inputs: mapping supplies the run’s inputs, evaluated by the code a real firing uses:
inputs:, when also set, apply over the mapped inputs.
scenario-terminal-uncovered
Warning. No scenario expects a run to end at this terminal node (a node with no outgoing edge). Coverage is read from what scenarios assert: a terminal named inexpect.completed, reached, node_outputs, messages or error_node.
lint-allow-invalid
Error. Alint:allow comment names a rule that does not exist (a typo silences nothing) or gives no reason. Use an ID from reliant workflow lint --rules, and say why after it.