Visor lets you declare simple expressions that fail a check when they evaluate to true. This works for any provider (ai, command, http_*, etc.) and is evaluated as part of the execution engine so dependents can be skipped reliably.
- Per check:
steps:
analyze-bug:
type: ai
schema: ./schemas/ticket-analysis.json
fail_if: output.error- Global (applies to all checks unless the check overrides with its own
fail_if):
fail_if: outputs["fetch-tickets"].errorInside the expression you can use:
output: the current check's structured output.- Includes
issuesand any other fields produced by the provider. - For custom schemas, all top-level JSON fields are preserved and exposed here.
- For command output that is JSON, fields are available directly; for plain text, treat
outputas a string.
- Includes
outputs: a map of dependency outputs keyed by check name. Each value is that check'soutputif present; otherwise the whole check result.
memory: accessor for the memory store (see Memory)memory.get(key, namespace?)- Get a valuememory.has(key, namespace?)- Check if key existsmemory.list(namespace?)- List keysmemory.getAll(namespace?)- Get all key-value pairs
inputs: workflow inputs (for workflows)env: environment variablesdebug: debug information (if available)debug.errors- Array of error messagesdebug.processingTime- Processing time in millisecondsdebug.provider- AI provider useddebug.model- AI model used
When used in if conditions (not fail_if), additional context is available:
branch: current branch namebaseBranch: target branch (default:main)filesChanged: array of changed file pathsfilesCount: number of changed filesevent: GitHub event context withevent_name,action,repository, etc.checkName,schema,group: check metadata
issues: shorthand foroutput.issuescriticalIssues,errorIssues,warningIssues,infoIssues,totalIssues: count of issues by severitymetadata: object containingcheckName,schema,group, issue counts,hasChanges,branch,event
contains(haystack, needle)- Case-insensitive substring checkstartsWith(s, prefix)- Case-insensitive prefix checkendsWith(s, suffix)- Case-insensitive suffix checklength(x)- Length of string, array, or object keys
always()- Always returnstruesuccess()- Returnstruefailure()- Returnsfalse
log(...args)- Print debug output prefixed with emoji. See Debugging Guide
hasIssue(issues, field, value)- Check if any issue has a field matching valuecountIssues(issues, field, value)- Count issues matching field/valuehasFileMatching(issues, pattern)- Check if any issue affects a file matching patternhasIssueWith(issues, field, value)- Alias forhasIssuehasFileWith(issues, pattern)- Alias forhasFileMatching
hasMinPermission(level)- Check if author has at least the specified permission levelisOwner()- Check if author is repository ownerisMember()- Check if author is organization memberisCollaborator()- Check if author is collaboratorisContributor()- Check if author has contributed beforeisFirstTimer()- Check if author is a first-time contributor
See Author Permissions for detailed usage.
Truthiness rules follow JavaScript: non-empty strings are truthy, "" and null/undefined are falsy, 0 is falsy.
For more control, use the complex form with additional options:
failure_conditions:
no_critical_issues:
condition: "criticalIssues > 0"
message: "Critical security issues found"
severity: error # error, warning, or info
halt_execution: true # Stop all execution immediatelyOptions:
condition: The expression to evaluate (required)message: Human-readable message when condition triggers (optional)severity: Issue severity level -error,warning, orinfo(default:error)halt_execution: Iftrue, stops all workflow execution immediately (default:false)
- The engine adds an issue to the check with ruleId
<checkName>_fail_if(orglobal_fail_iffor the global rule). - Issue severity is
errorby default (configurable with complex form). - Direct dependents of that check are skipped with reason
dependency_failed. - Skipped checks do not execute their providers; they appear as skipped in the details table.
- If
halt_execution: true, the entire workflow stops immediately.
Only direct dependencies gate execution. Transitive checks are gated through the chain (i.e., if A fails B, and C depends on B, C will also be skipped once B is skipped).
Fail when AI (custom schema) reports an error:
steps:
analyze-bug:
type: ai
schema: ./schemas/ticket-analysis.json
fail_if: output.error
log-results:
type: command
depends_on: [analyze-bug]
exec: echo "OK"Fail when a dependency produced an error flag:
fail_if: outputs["fetch-tickets"].errorFail on text output pattern:
steps:
lint:
type: command
exec: run-linter
fail_if: contains(output, "ERROR:")When a check uses forEach: true, the engine evaluates fail_if once on the aggregated result (after all items complete). You can write expressions against the aggregated output (often an array), e.g.:
fail_if: output.some(x => x.status === 'fail')Per‑item fail logic is not evaluated via fail_if; use if: to skip item work or emit issues inside the per‑item action.
You can write multi-line expressions with debug statements:
fail_if: |
log("Checking output:", output);
log("Issue count:", output.issues?.length);
output.issues?.some(i => i.severity === 'critical')The last expression determines the boolean result. Lines are joined using the comma operator.
- Fail conditions are evaluated during dependency-aware execution as part of the engine (not only in providers), ensuring dependents are reliably skipped even if a provider path didn't attach issues.
- Issue ruleIds are consistent:
<checkName>_fail_iffor per-check andglobal_fail_iffor global. - Expressions support optional chaining (
?.) and nullish coalescing (??) for safe property access. - If expression evaluation fails, the condition is treated as
false(fail-safe behavior).
- Author Permissions - Permission helper functions
- Debugging Guide - Using
log()and other debugging techniques - Memory - Memory store access in expressions
- Configuration - Full configuration reference