Public preview Grafana Cloud
Last reviewed: November 9, 2026

Enforce severity before resolve

Note

Grafana Workflows is currently in public preview. Grafana Labs offers limited support, and breaking changes might occur prior to the feature being made generally available.

Stop incidents from being resolved while their severity is still Pending. Reopens the incident and tells the team why.

This Grafana Workflows blueprint is in the Incidents category of the Blueprints library.

Before you begin

To use this blueprint, you need:

Steps

Steps run in order from first to last. A step inside a switch branch runs only when Workflows chooses that branch.

StepIDTypeDescription
Is severity still Pending?severity-is-pendingfilterContinues only when the incident was resolved with a Pending severity. Change the regex to also block other severities, for example ‘(?i)^(pending|unknown)$’.
Revert the status changereopenincident.set-fieldSets the incident back to the status it had before it was resolved. No changes needed.
Explain on the timelineexplain-on-timelineincident.add-noteAdds a timeline note that explains why the incident was reopened. Edit the wording to match your policy.
Has an incident channel?has-slack-channelfilterStops here when the incident has no linked Slack channel. No changes needed.
Tell the incident channeltell-the-channelslack.message.postExplains the reopen in the incident Slack channel. Edit the wording to match your policy.

For the inputs and outputs of each step type, refer to Step and trigger types reference.

Before you turn on the workflow, customize any steps that need input from you, such as a channel ID or a schedule name. Each step’s description says what to set.

Workflow definition

Save this definition as a YAML file to create the workflow with gcx, the Workflows API, or Terraform, or give it to a coding agent as an example. Replace FOLDER_UID with the UID of the folder for the workflow. Blueprints start disabled, so finish customizing the steps before you set enabled to true. For details, refer to Manage workflows as code.

YAML
apiVersion: workflows.ext.grafana.app/v1alpha1
kind: Definition
metadata:
  name: enforce-severity-before-resolve
  annotations:
    grafana.app/folder: <FOLDER_UID>
spec:
  name: Enforce severity before resolve
  enabled: false
  runOnceFor: "enforce-severity:${inputs.data.incidentID}:${has(inputs.data.closedTime) ? inputs.data.closedTime : ''}"
  startWhen:
    matchingRules:
      - eventNameRegex: 'grafana_irm_app\.incident\.updated'
        filter: "inputs.data.eventKind == 'incident_updated_status' && inputs.data.newStatus == 'resolved'"
    schedules: []
  steps:
    - id: severity-is-pending
      type: filter
      name: Is severity still Pending?
      description: >-
        Continues only when the incident was resolved with a Pending severity. Change the regex
        to also block other severities, for example '(?i)^(pending|unknown)$'.
      inputs:
        condition: "resources.incident.event.severity.matches('(?i)^pending$')"
    - id: reopen
      type: incident.set-field
      name: Revert the status change
      description: Sets the incident back to the status it had before it was resolved. No changes needed.
      inputs:
        incidentID: '${inputs.data.incidentID}'
        field: 'status'
        value: '${inputs.data.previousStatus}'
    - id: explain-on-timeline
      type: incident.add-note
      name: Explain on the timeline
      description: Adds a timeline note that explains why the incident was reopened. Edit the wording to match your policy.
      inputs:
        incidentID: '${inputs.data.incidentID}'
        message: 'This incident was reopened automatically because it was resolved while its severity was still **Pending**. Set a severity, then resolve it again.'
    - id: has-slack-channel
      type: filter
      name: Has an incident channel?
      description: Stops here when the incident has no linked Slack channel. No changes needed.
      inputs:
        condition: "has(resources.incident.event.slackChannelID)"
    - id: tell-the-channel
      type: slack.message.post
      name: Tell the incident channel
      description: Explains the reopen in the incident Slack channel. Edit the wording to match your policy.
      inputs:
        channelID: '${resources.incident.event.slackChannelID}'
        messageText: ':warning: This incident was reopened because it was resolved with severity *Pending*. Set a severity on the <${resources.incident.event.overviewURL}|incident page>, then resolve it again.'

How it works

This section explains when the workflow runs and how its steps work together.

When it runs

This blueprint starts on the grafana_irm_app.incident.updated event.

Its trigger filter starts a run only when this CEL expression is true:

text
inputs.data.eventKind == 'incident_updated_status' && inputs.data.newStatus == 'resolved'

It runs at most once for each run-once key. This blueprint’s key is enforce-severity:${inputs.data.incidentID}:${has(inputs.data.closedTime) ? inputs.data.closedTime : ''}, so a later event with the same key doesn’t start another run. For details, refer to Deduplicate workflow runs.

How the steps fit together

These notes cover how the steps connect and which Workflows behaviors the blueprint relies on.

  • Triggers on incident status changes (eventKind incident_updated_status) that move an incident to the resolved status category.
  • The event carries newStatus and previousStatus as status categories (active or resolved), plus closedTime.
  • A filter step checks the severity on the auto-loaded incident at resources.incident.event.
  • Severity is matched case-insensitively with matches('(?i)^pending$') because CEL has no lowercase function.
  • When severity is Pending, the workflow sets the status back to the previous category, adds a timeline note, and posts in the incident Slack channel when one exists.
  • Reopening emits another status event, but its newStatus is active, so the trigger filter ignores it and there is no loop.
  • IRM can emit two status events for one resolution, so runOnceFor keys on the incident ID plus closedTime, which both events share.
  • A later resolution has a new closedTime, so it is checked again.
  • runOnceFor is rendered for every incident event before the trigger filter runs, so has() supplies an empty value for events without closedTime.
  • Customize the note and Slack message text.

Next steps