GitHub Agentic Workflows

Triggers

The on: section uses standard GitHub Actions syntax to define workflow triggers. For example:

on:
issues:
types: [opened]

GitHub Agentic Workflows supports all standard GitHub Actions triggers plus additional enhancements for reactions, cost control, and advanced filtering.

Run workflows manually from the GitHub UI, API, or via gh aw run/gh aw trial. Full syntax reference.

Basic trigger:

on:
workflow_dispatch:

With input parameters:

on:
workflow_dispatch:
inputs:
topic:
description: 'Research topic'
required: true
type: string
priority:
description: 'Task priority'
required: false
type: choice
options:
- low
- medium
- high
default: medium
deploy_env:
description: 'Target environment'
required: false
type: environment
default: staging

Access inputs in your markdown content with ${{ github.event.inputs.INPUT_NAME }}:

Research the following topic: "${{ github.event.inputs.topic }}"

Supported input types: string (free-form text), boolean (checkbox), choice (dropdown with predefined options), and environment (dropdown populated from repository Settings → Environments).

The environment input returns the environment name as a string and supports a default value. Unlike manual-approval:, it does not enforce environment protection rules — it only provides the environment name for use in your workflow logic.

Run workflows on a recurring schedule using human-friendly expressions or cron syntax.

Fuzzy Scheduling:

Fuzzy schedules scatter execution times to avoid load spikes. Use around <time> for a preferred time with ±1 hour flexibility, or between <a> and <b> to scatter within a window (such as business hours):

on:
schedule: daily # Compiler picks a scattered time
# schedule: daily around 14:00 # ±1 hour around 2pm
# schedule: daily between 9:00 and 17:00 # Scatters within 9am-5pm

The compiler assigns each workflow a unique, deterministic execution time based on the file path, ensuring load distribution and consistency across recompiles. UTC offsets are supported on any time expression (e.g., daily between 9am and 5pm utc-5).

For a fixed time, use standard cron syntax. Add an optional timezone field to interpret the cron in a specific IANA timezone instead of UTC:

on:
schedule:
- cron: "30 6 * * 1" # Monday at 06:30 UTC
- cron: "0 9 15 * *" # 15th of month at 09:00 UTC
- cron: "30 9 * * 1-5"
timezone: "America/New_York" # 9:30 AM EST/EDT Mon-Fri
FormatExampleResultNotes
Hourly (Fuzzy)hourly58 */1 * * *Compiler assigns scattered minute
Daily (Fuzzy)daily43 5 * * *Compiler assigns scattered time
daily around 14:0020 14 * * *Scattered within ±1 hour (13:00-15:00)
daily between 9:00 and 17:0037 13 * * *Scattered within range (9:00-17:00)
daily between 9am and 5pm utc-512 18 * * *With UTC offset (9am-5pm EST → 2pm-10pm UTC)
daily around 3pm utc-533 19 * * *With UTC offset (3 PM EST → 8 PM UTC)
Weekly (Fuzzy)weekly or weekly on monday43 5 * * 1Compiler assigns scattered time
weekly on friday around 5pm18 16 * * 5Scattered within ±1 hour
Intervalsevery 10 minutes*/10 * * * *Minimum 5 minutes
every 2h53 */2 * * *Fuzzy: scattered minute offset
0 */2 * * *0 */2 * * *Cron syntax for fixed times

Time formats: HH:MM (24-hour), midnight, noon, 1pm-12pm, 1am-12am UTC offsets: Add utc+N or utc-N to any time (e.g., daily around 14:00 utc-5)

Human-friendly formats are automatically converted to standard cron expressions, with the original format preserved as a comment in the generated workflow file.

Trigger on issue events. Full event reference.

on:
issues:
types: [opened, edited, labeled]

Prevent concurrent modifications to an issue during workflow execution by setting lock-for-agent: true:

on:
issues:
types: [opened, edited]
lock-for-agent: true

The issue is locked at workflow start and unlocked after completion (or before safe-output processing); the unlock step uses always() so cleanup runs even on failure. Useful for workflows that make multiple sequential updates or need to prevent race conditions.

Trigger on pull request events. Full event reference.

When triggered by a pull request event, the coding agent has access to both the PR branch and the default branch.

on:
pull_request:
types: [opened, synchronize, labeled]
names: [ready-for-review, needs-review]
reaction: "rocket"

Pull request workflows block forks by default for security. Use the forks: field to allow specific fork patterns:

on:
pull_request:
types: [opened, synchronize]
forks: ["trusted-org/*"] # Allow forks from trusted-org

Use ["owner/repo"] for a specific repository, ["owner/*"] for an entire org/user, or ["*"] to allow all forks (use with caution). Omit forks: for the default behavior (same-repository PRs only). The compiler uses repository ID comparison so fork detection is unaffected by repository renames.

The triggers issue_comment:, pull_request_review_comment:, and discussion_comment: activate workflows when comments are created or edited.

Note that issue_comment events also fire for comments on pull requests (GitHub models PR comments as issue comments). When a comment is on a pull request, the coding agent has access to both the PR branch and the default branch.

on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
discussion_comment:
types: [created]
reaction: "eyes"

For issue_comment events, you can lock the parent issue during workflow execution:

on:
issue_comment:
types: [created, edited]
lock-for-agent: true

This prevents concurrent modifications to the issue while processing the comment. The locking behavior is identical to the issues: trigger (see Issue Locking above for full details).

Note: Pull request comments are silently skipped as pull requests cannot be locked via the issues API.

Trigger workflows after another workflow completes. Full event reference.

on:
workflow_run:
workflows: ["CI"]
types: [completed]
branches:
- main
- develop

Workflows with workflow_run triggers include automatic security protections: workflows must list at least one non-empty entry (empty or missing values are rejected at compile time, since GitHub silently disables such triggers); the compiler injects repository ID and fork checks to reject cross-repository or fork-triggered runs; and branches is recommended to limit triggering branches (the compiler warns when omitted, or errors in strict mode). See the Security Architecture for details.

Use conclusion: to restrict the trigger to specific workflow run outcomes. Accepts a single value or a list. Compiles into a guarded if: condition — other events in the same on: block are unaffected.

on:
workflow_run:
workflows: ["CI"]
types: [completed]
conclusion: [failure, cancelled]

Valid values: success, failure, cancelled, skipped, timed_out, action_required, neutral, stale.

Deployment Status Triggers (deployment_status:)

Section titled “Deployment Status Triggers (deployment_status:)”

Trigger workflows when a GitHub deployment status changes. Full event reference.

on:
deployment_status:

Use state: to restrict the trigger to specific deployment states (single value or list). The compiler emits a guarded if: condition so other combined triggers (such as workflow_dispatch) pass through unaffected.

on:
deployment_status:
state: [error, failure] # Or a single value: state: failure
workflow_dispatch: # Safely combined — guard ensures dispatch passes through

Valid state values: error, failure, pending, success, inactive, in_progress, queued, waiting.

Workflows triggered by deployment_status need deployments: read to access the event payload:

permissions:
contents: read
deployments: read

Repository Dispatch Trigger (repository_dispatch:)

Section titled “Repository Dispatch Trigger (repository_dispatch:)”

Trigger a workflow from outside GitHub using a single authenticated API call. Any external system that can make an HTTP POST request—Jira, PagerDuty, Slack, or a custom API—can start an agentic workflow this way. Full event reference.

on:
repository_dispatch:
types: [jira-issue-created]

Omit types: to fire on any event_type.

Call the GitHub dispatch API with a repo-scoped PAT (classic) or a token with contents: write permission:

POST https://api.github.com/repos/<owner>/<repo>/dispatches
Authorization: Bearer <token>
Content-Type: application/json
{
"event_type": "jira-issue-created",
"client_payload": { "issue_key": "PROJ-123", "summary": "Fix the thing" }
}

Reference client_payload fields in your workflow markdown using standard GitHub Actions expressions:

Issue ${{ github.event.client_payload.issue_key }}: ${{ github.event.client_payload.summary }}

The slash_command: trigger creates workflows that respond to /command-name mentions in issues, pull requests, and comments.

By default, command triggers listen to all comment-related events, which can create noise from skipped runs. Use the events: field to restrict where commands are active:

on:
slash_command:
name: investigate
events: [issues, issue_comment] # Only respond in issue contexts
# strategy: centralized # Optional: route via generated central trigger workflow

See Command Triggers for complete documentation including event filtering, context text, reactions, and examples.

The label_command: trigger activates a workflow when a specific label is applied to an issue, pull request, or discussion, and automatically removes that label so it can be re-applied to re-trigger. This treats a label as a one-shot command rather than a persistent state marker.

# Fires on issues, pull_request, and discussion by default
on:
label_command: deploy
# Restrict to specific event types
on:
label_command:
name: deploy
events: [pull_request]
# Disable automatic label removal (label stays on the item after activation)
on:
label_command:
name: deploy
remove_label: false
# Shorthand string form
on: "label-command deploy"

The compiler generates issues, pull_request, and/or discussion events with types: [labeled], adds a workflow_dispatch trigger with item_number for manual testing, and injects a label removal step in the activation job. The matched label name is exposed as needs.activation.outputs.label_command.

The remove_label field (boolean, default true) controls whether the label is automatically removed after activation. Set to false to keep the label on the item — useful when the label represents persistent state rather than a one-shot command. When remove_label: false, the workflow does not need issues: write or pull-requests: write permissions for label removal.

label_command can be combined with slash_command: — the workflow activates when either condition is met. See LabelOps for patterns and examples.

Triggers can be filtered by label names, and more. These filters compile into guarded if: conditions that ensure the workflow only runs when the specified criteria are met, while allowing other events to pass through unaffected.

Filter issue and pull request triggers by label names using the names: field. Unlike label_command, the label stays on the item after the workflow runs.

on:
issues:
types: [labeled, unlabeled]
names: [bug, critical, security]

Use convenient shorthand for label-based triggers:

on: issue labeled bug
on: issue labeled bug, enhancement, priority-high # Multiple labels
on: pull_request labeled needs-review, ready-to-merge

All shorthand formats compile to standard GitHub Actions syntax and automatically include the workflow_dispatch trigger. Supported for issue, pull_request, and discussion events. See LabelOps workflows for automation examples.

For conditions that can be expressed directly with GitHub Actions context, use if: without a custom job:

---
on:
pull_request:
types: [opened, synchronize]
if: github.event.pull_request.draft == false
---

Filtering with Search Queries (skip-if-match:, skip-if-no-match:)

Section titled “Filtering with Search Queries (skip-if-match:, skip-if-no-match:)”

For conditions based on GitHub search results, use skip-if-match: or skip-if-no-match: in the on: section. These accept standard GitHub search query syntax and produce the same skipped-not-failed behavior.

Filtering by Repository Access Roles (on.roles:, on.skip-roles:)

Section titled “Filtering by Repository Access Roles (on.roles:, on.skip-roles:)”

Controls who can trigger agentic workflows based on repository permission level. Defaults to [admin, maintainer, write]. Use skip-roles: to exempt team members from checks that should only apply to external contributors.

on:
issues:
types: [opened]
roles: [admin, maintainer, write] # Default; use `all` to allow any user (! caution)
# skip-roles: [admin, maintainer, write]

Available roles: admin, maintainer/maintain, write, triage, read, all. Workflows with unsafe triggers (push, issues, pull_request) automatically enforce permission checks. Failed checks cancel the workflow with a warning.

Filtering by Bot (on.bots:, on.skip-bots:)

Section titled “Filtering by Bot (on.bots:, on.skip-bots:)”

Configure which GitHub bot accounts can trigger workflows — useful for allowing specific automation bots while maintaining security controls. Use skip-bots: for the inverse:

on:
issues:
types: [opened]
bots: ["dependabot[bot]", "renovate[bot]", "agentic-workflows-dev[bot]"]
# skip-bots: [github-actions, copilot, dependabot]

The [bot] suffix is optional — github-actions matches github-actions[bot] automatically.

Filtering by Author Associations (on.skip-author-associations)

Section titled “Filtering by Author Associations (on.skip-author-associations)”

You can skip workflow execution when a specific event is triggered by an author with a matching event payload author_association field (for example github.event.comment.author_association, github.event.issue.author_association, or github.event.pull_request.author_association).

on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
skip-author-associations:
issue_comment: contributor
pull_request_review_comment: [first_time_contributor, none]

Inject deterministic filtering steps directly into the pre-activation job — see Pre-Activation Steps for full syntax and examples. This is the recommended approach for lightweight filtering since it saves one workflow job versus the multi-job pattern below.

For complex custom trigger filtering you can use a separate jobs: entry when filtering requires heavy tooling (checkout, compiled tools, multiple runners):

.github/workflows/smart-responder.md
---
on:
issues:
types: [opened]
safe-outputs:
add-comment:
jobs:
filter:
runs-on: ubuntu-latest
outputs:
should-run: ${{ steps.check.outputs.result }}
steps:
- id: check
env:
LABELS: ${{ toJSON(github.event.issue.labels.*.name) }}
run: |
if echo "$LABELS" | grep -q '"bug"'; then
echo "result=true" >> "$GITHUB_OUTPUT"
else
echo "result=false" >> "$GITHUB_OUTPUT"
fi
if: needs.filter.outputs.should-run == 'true'
---
# Bug Issue Responder
Triage bug report: "${{ github.event.issue.title }}" and add-comment with a summary of the next steps.

The compiler automatically adds the filter job as a dependency of the activation job, so when the condition is false the workflow run is skipped (not failed), keeping the Actions tab clean.

Trigger support additional options for reactions, status comments, authentication tokens, and more. These options are configured in the same on: block as the trigger and apply to all triggers defined within that block.

Enable emoji reactions on triggering items (issues, PRs, comments, discussions) to provide visual workflow status feedback:

on:
issues:
types: [opened]
reaction: "eyes"

The reaction is added to the triggering item. Use none to disable reactions entirely.

Available reactions: +1 , -1 , laugh , confused , heart , hooray , rocket , eyes

Post a started/completed comment on the triggering item with a link to the workflow run:

on:
issues:
types: [opened]
reaction: "eyes"
status-comment: true

When status-comment: true, the activation job posts a comment on workflow start and updates it on completion. reaction: and status-comment: are independent settings. For slash_command and label_command, both default to enabled (with reaction: eyes); disable either explicitly:

on:
slash_command: my-bot
reaction: none # disable the eyes reaction
status-comment: false # disable the status comment

For all other triggers, status-comment must be explicitly set to true. Use an object to selectively disable specific targets (each field defaults to true):

on:
issues:
types: [opened]
pull_request:
types: [opened]
discussion:
types: [created]
status-comment:
issues: true # post on issue events (default)
pull-requests: false # skip pull request events
discussions: false # skip discussion events
FieldTypeDefaultDescription
issuesbooleantrueEnable status comments for issues and issue_comment events
pull-requestsbooleantrueEnable status comments for pull_request and pull_request_review_comment events
discussionsbooleantrueEnable status comments for discussion and discussion_comment events

Activation Token (on.github-token:, on.github-app:)

Section titled “Activation Token (on.github-token:, on.github-app:)”

Configure a custom GitHub token or GitHub App for the activation job and all skip-if search checks — reaction, status comment, and search steps share the same token (default: workflow’s GITHUB_TOKEN). Use github-token: for a PAT or github-app: to mint a short-lived installation token:

on:
issues:
types: [opened]
reaction: "eyes"
github-token: ${{ secrets.MY_TOKEN }}
on:
issues:
types: [opened]
reaction: "rocket"
github-app:
client-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_KEY }}

The github-app object accepts client-id, private-key, and optionally owner and repositories — the same fields used elsewhere in the framework (app-id is a deprecated alias for client-id). The token is minted once in the pre-activation job.

Both fields can be defined in a shared agentic workflow and are inherited by importers (first-wins). A CentralRepoOps shared workflow can define the app config once and all importers benefit:

# shared-ops.md - define app config once
on:
workflow_call:
github-app:
client-id: ${{ secrets.ORG_APP_ID }}
private-key: ${{ secrets.ORG_APP_PRIVATE_KEY }}
owner: myorg
# any-workflow.md - inherits github-app from the import
imports:
- .github/workflows/shared/shared-ops.md
on:
schedule: every 30 minutes
skip-if-no-match:
query: "org:myorg label:agent-fix is:issue is:open"
scope: none

[!NOTE] github-token and github-app affect only the activation job (reactions, status comments, and skip-if searches). For the agent job, configure tokens via tools.github.github-token/tools.github.github-app or safe-outputs.github-token/safe-outputs.github-app. See Authentication for a full overview.

Automatically disable workflow triggering after a deadline to control costs.

on: weekly on monday
stop-after: "+25h" # 25 hours from compilation time

Accepts absolute dates (YYYY-MM-DD, MM/DD/YYYY, DD/MM/YYYY, January 2 2006, 1st June 2025, ISO 8601) or relative deltas (+7d, +25h, +1d12h30m) calculated from compilation time. The minimum granularity is hours - minute-only units (e.g., +30m) are not allowed. Recompiling the workflow resets the stop time.

Require manual approval before workflow execution using GitHub environment protection rules:

on:
workflow_dispatch:
manual-approval: production

Sets the environment on the activation job for human-in-the-loop approval before execution. The value must match a configured environment in repository Settings → Environments (approval rules, required reviewers, wait timers). See GitHub’s environment documentation for configuration details.

Conditionally skip workflow execution when a GitHub search query has matches. Useful for preventing duplicate scheduled runs or waiting for prerequisites.

on: daily
skip-if-match: 'is:issue is:open in:title "[daily-report]"' # Skip if any match
on: weekly on monday
skip-if-match:
query: "is:pr is:open label:urgent"
max: 3 # Skip if 3 or more PRs match

A pre-activation check runs the query against the current repository. If matches reach or exceed the threshold (default max: 1), the workflow is skipped. All standard GitHub search qualifiers are supported (is:, label:, in:title, author:, etc.).

Use scope: none to remove the automatic repo qualifier and search org-wide. For cross-repo or org-wide searches that need elevated permissions, configure github-token or github-app at the top-level on: section — the same token is shared across all skip-if checks and the activation job:

on:
schedule: every 15 minutes
skip-if-match:
query: "org:myorg label:ops:in-progress is:issue is:open"
scope: none
github-app:
client-id: ${{ secrets.WORKFLOW_APP_ID }}
private-key: ${{ secrets.WORKFLOW_APP_PRIVATE_KEY }}
owner: myorg
FieldLocationDescription
scope: noneinside skip-if-matchDisables the automatic repo:owner/repo qualifier
github-tokentop-level on:Custom PAT or token for all skip-if searches (e.g. ${{ secrets.CROSS_ORG_TOKEN }})
github-apptop-level on:Mints a short-lived installation token shared across all skip-if steps; requires client-id and private-key

github-token and github-app are mutually exclusive. String shorthand always uses the default GITHUB_TOKEN scoped to the current repository.

Skip-If-No-Match Condition (skip-if-no-match:)

Section titled “Skip-If-No-Match Condition (skip-if-no-match:)”

Conditionally skip workflow execution when a GitHub search query has no matches (or fewer than the minimum required). This is the opposite of skip-if-match.

on: weekly on monday
skip-if-no-match: 'is:pr is:open label:ready-to-deploy' # Skip if no matches
on:
workflow_dispatch:
skip-if-no-match:
query: "is:issue is:open label:urgent"
min: 3 # Only run if 3 or more issues match

If matches are below the threshold (default min: 1), the workflow is skipped. Can be combined with skip-if-match for complex conditions. scope: none, github-token, and github-app work identically to skip-if-match above — a single mint step is shared when both are present.

Inject custom deterministic steps directly into the pre-activation job. Steps run after all built-in checks (membership, stop-time, skip-if, etc.) and before agent execution. This saves one workflow job compared to the multi-job pattern and keeps filtering logic co-located with the trigger configuration.

on:
issues:
types: [opened]
steps:
- name: Check issue label
id: label_check
env:
LABELS: ${{ toJSON(github.event.issue.labels.*.name) }}
run: echo "$LABELS" | grep -q '"bug"'
# exits 0 (outcome: success) if the label is found, 1 (outcome: failure) if not
if: needs.pre_activation.outputs.label_check_result == 'success'

Each step with an id automatically gets an output <id>_result wired to ${{ steps.<id>.outcome }} (values: success, failure, cancelled, skipped). This lets you gate the workflow on whether the step succeeded or failed via its exit code.

To pass an explicit value rather than relying on exit codes, set a step output (e.g., echo "has_bug_label=true" >> "$GITHUB_OUTPUT") and re-expose it via jobs.pre-activation.outputs:

jobs:
pre-activation:
outputs:
has_bug_label: ${{ steps.label_check.outputs.has_bug_label }}
if: needs.pre_activation.outputs.has_bug_label == 'true'

Explicit outputs in jobs.pre-activation.outputs take precedence over auto-wired <id>_result outputs on key collision.

Pre-Activation and Activation Dependencies (on.needs:)

Section titled “Pre-Activation and Activation Dependencies (on.needs:)”

Add custom jobs that both pre_activation and activation should depend on. Use this when on.github-app credentials come from a job output (for example, a secret-manager fetch job).

on:
workflow_dispatch:
needs: [secrets_fetcher]
github-app:
client-id: ${{ needs.secrets_fetcher.outputs.app_id }}
private-key: ${{ needs.secrets_fetcher.outputs.private_key }}
jobs:
secrets_fetcher:
runs-on: ubuntu-latest
outputs:
app_id: ${{ steps.fetch.outputs.app_id }}
private_key: ${{ steps.fetch.outputs.private_key }}
steps:
- id: fetch
run: |
echo "app_id=123" >> "$GITHUB_OUTPUT"
echo "private_key=***" >> "$GITHUB_OUTPUT"

on.needs values must reference custom jobs from top-level jobs:. Built-in jobs are rejected.

Pre-Activation Permissions (on.permissions:)

Section titled “Pre-Activation Permissions (on.permissions:)”

Grant additional GitHub token permission scopes to the pre-activation job. Use when on.steps: make GitHub API calls that require permissions beyond the defaults.

on:
schedule: every 30m
permissions:
issues: read
pull-requests: read
steps:
- name: Search for candidate issues
id: search
uses: actions/github-script@v8
with:
script: |
const issues = await github.rest.issues.listForRepo(context.repo);
core.setOutput('has_issues', issues.data.length > 0 ? 'true' : 'false');
jobs:
pre-activation:
outputs:
has_issues: ${{ steps.search.outputs.has_issues }}
if: needs.pre_activation.outputs.has_issues == 'true'

Supported permission scopes: actions, checks, contents, deployments, discussions, issues, packages, pages, pull-requests, repository-projects, security-events, statuses.

on.permissions is merged on top of any permissions already required by the pre-activation job (e.g., contents: read for dev-mode checkout, actions: read for rate limiting).

Instead of writing full YAML trigger configurations, you can use natural-language shorthand strings with on:. The compiler expands these into standard GitHub Actions trigger syntax and automatically includes workflow_dispatch so the workflow can also be run manually.

For label-based shorthands (on: issue labeled bug, on: pull_request labeled needs-review), see Label Filtering above. For the label-command pattern, see Label Command Trigger above.

on: push to main # Push to specific branch
on: push tags v* # Push tags matching pattern
on: pull_request opened # PR with activity type
on: pull_request merged # PR merged (maps to closed + merge condition)
on: pull_request affecting src/** # PR touching paths (opened, synchronize, reopened)
on: pull_request opened affecting docs/** # Activity type + path filter

pull is an alias for pull_request. Valid activity types: opened, edited, closed, reopened, synchronize, assigned, unassigned, labeled, unlabeled, review_requested, merged.

on: issue opened # Issue with activity type
on: issue opened labeled bug # Issue opened with specific label (adds job condition)
on: discussion created # Discussion with activity type

Valid issue types: opened, edited, closed, reopened, assigned, unassigned, labeled, unlabeled, deleted, transferred. Valid discussion types: created, edited, deleted, transferred, pinned, unpinned, labeled, unlabeled, locked, unlocked, category_changed, answered, unanswered.

on: manual # workflow_dispatch (run manually)
on: manual with input version # workflow_dispatch with a string input
on: workflow completed ci-test # Trigger after another workflow completes
on: comment created # Issue or PR comment created
on: release published # Release event (published, created, prereleased, etc.)
on: repository starred # Repository starred (maps to watch event)
on: repository forked # Repository forked
on: dependabot pull request # PR from Dependabot (adds actor condition)
on: security alert # Code scanning alert
on: code scanning alert # Alias for security alert (code scanning alert)
on: api dispatch custom-event # Repository dispatch with custom event type
on: "deployment failed" # deployment_status with state == 'failure' guard
on: "deployment error" # deployment_status with state == 'error' guard
on: "deployment failed or error" # deployment_status with state == 'failure' or 'error' guard