---
title: Loop Protection
description: How Flexkit stops data-change automations from retriggering themselves and running without limit.
---

# Loop Protection

A data-change automation can update the same records that woke it up. Without a stop, that write starts the automation again, and the next write starts it again.

Loop protection is Flexkit's built-in brake for that path. It applies to automations that run **when your data changes**. Scheduled, manual, and webhook runs are not rate-limited this way.

Skipped runs appear in Studio with status **skipped**. They do not use AI credits and do not count toward the automation's run total.

## Why it exists

The risky setup is an automation that:

1. Listens for creates, updates, or deletes on an entity.
2. Has **Auto-approve** mutations enabled.
3. Writes back to records that match its own trigger.

One misconfigured automation like that can keep starting new runs until the team's prepaid AI credits are gone.

## What Flexkit stops

When an automation writes data, Flexkit remembers that it caused the change. The next data-change dispatch then applies these rules:

- **Self-trigger.** The same automation does not run again from its own write.
- **Cycle.** An automation already involved in this chain of writes does not run again.
- **Chain length.** After three automations have run in one chain, further data-change automations are skipped.

Unrelated automations still run. If automation A updates a product status and automation B only sends a notification, B is not blocked.

Human edits, API writes, and chat-agent writes have no automation origin, so they always start a new chain.

## Example: an automation that would trigger itself

Imagine a catalog with `product` records (`name`, `sku`, `description`, `urlPath`, and so on).

You create **SEO Enricher**:

- Trigger: `product` is **updated**
- Mutations: **Auto-approve**
- Instructions: "When a product changes, rewrite `description` and set `urlPath` from `name`."

An editor changes the name of _Vitamin C 1000mg_ (`sku: VITC-1000`). That edit is a normal Studio save, so SEO Enricher is allowed to run.

The run updates the same product:

```graphql
mutation {
  updateProducts(
    where: { sku: { eq: "VITC-1000" } }
    update: {
      description: { set: "Vitamin C 1000mg — daily antioxidant support." }
      urlPath: { set: "vitamin-c-1000mg" }
    }
  ) {
    products {
      sku
    }
  }
}
```

That write is another `product` update, so it matches SEO Enricher's trigger again. Loop protection skips the second run as a **self-trigger**.

In Studio you will see:

> Skipped because this automation caused the data change that would re-trigger it.

Without that skip, the second run would rewrite `description` again, start a third run, and continue until credits ran out.

## What is still allowed

A second automation, **Review Moderator**, can listen for `review` creates or updates and set `review.status` to `moderated` or `rejected`. That write belongs to Review Moderator, so it does not start SEO Enricher.

You can also chain automations across entities. For example:

1. A teammate creates a `review`.
2. **Review Moderator** sets `review.status` to `moderated`.
3. **Rating Rollup** updates the related `product` from the new reviews.
4. **SEO Enricher** rewrites that product's `description` and `urlPath`.
5. SEO Enricher's own product write would start SEO Enricher again — that run is skipped as a **self-trigger**.

If a fourth data-change automation tried to run from that last product write, it would be skipped because the chain is already three automations long.

If two automations keep writing records that wake each other — Rating Rollup updates a `product`, SEO Enricher updates that `product`, Rating Rollup would run again — the second pass is skipped as a **cycle**.

## Many records, one run

When many matching records change together, Flexkit groups them. A bulk update of 40 products does not start 40 SEO Enricher runs. You get one run whose trigger payload lists every affected id.

Changes that arrive in a short window for the same automation, entity, and event are grouped the same way. Design instructions to handle a list of ids, or use a bulk GraphQL action when the list is large.

## Rate and concurrency limits

Data-change runs also have caps so a burst of writes cannot start unlimited work:

- 20 data-change runs per minute per automation
- 60 data-change runs per minute per project
- 3 runs of the same automation at once (`running` or waiting for approval)

If a cap is hit, Flexkit records a skipped run instead of starting another. Manual **Run now**, schedules, and webhooks are not included in these caps.

## When Flexkit disables the automation

If the same automation is skipped 10 times in 10 minutes for a self-trigger, cycle, or chain-length reason, Flexkit turns it off. Rate and concurrency skips are still recorded in Studio, but they do not disable the automation. One write that would re-trigger the same automation across several entity types counts as a single skip, not one skip per entity type.

- The automation's **Enabled** switch is set to off.
- The project activity log records that loop protection disabled it.
- Billing recipients on the team get an email with the automation name, project, and skip reason.

Fix the trigger or instructions so the automation no longer writes the records that start it — or turn off **Auto-approve** so each write is reviewed — then enable it again in Studio.

## How to design data-change automations

Prefer triggers that do not match the records you write. **Review Moderator** updating `review.status` is safer if a follow-up automation, not the same one, updates the related `product`.

Use **Require approval** while you are still shaping the instructions. Auto-approve is convenient, and it is also the setting that can loop without a person in the middle.

If a run is skipped, open it in Studio and read the summary. The reason tells you whether the automation wrote its own trigger, closed a cycle, hit the chain limit, or hit a rate cap.


---

[View full sitemap](/docs/sitemap.md)
