phacility/phabricator · warning · PhabricatorSystemActionRateLimitException

You are performing too many actions too quickly.

Error message

You are performing too many actions too quickly.

What it means

PhabricatorSystemActionEngine::willTakeAction() implements Phabricator's flood control: before an action executes it loads each actor's accumulated score for that PhabricatorSystemAction and, if the actor is already blocked, throws PhabricatorSystemActionRateLimitException. Scores decay over time, so the block is temporary and self-clearing.

Source

Thrown at src/applications/system/engine/PhabricatorSystemActionEngine.php:52

   * @{class:PhabricatorSystemActionRateLimitException}.
   *
   * @param list<string> List of actors.
   * @param PhabricatorSystemAction Action being taken.
   * @param float Score or credit, see above.
   * @return void
   */
  public static function willTakeAction(
    array $actors,
    PhabricatorSystemAction $action,
    $score) {

    // If the score for this action is negative, we're giving the user a credit,
    // so don't bother checking if they're blocked or not.
    if ($score >= 0) {
      $blocked = self::loadBlockedActors($actors, $action, $score);
      if ($blocked) {
        foreach ($blocked as $actor => $actor_score) {
          throw new PhabricatorSystemActionRateLimitException(
            $action,
            $actor_score);
        }
      }
    }

    if ($score != 0) {
      $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
        self::recordAction($actors, $action, $score);
      unset($unguarded);
    }
  }

  public static function loadBlockedActors(
    array $actors,
    PhabricatorSystemAction $action,
    $score) {

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Slow down or pause the loop so scores decay below the limit, then resume
  2. Raise (or disable) the specific action limit in Config > Actions
  3. Batch work so a single action covers many objects instead of one action per object
  4. Run flood-heavy scripted work under a dedicated actor with its own score budget

Example fix

// before: one rate-limited action per row, no guard
foreach ($rows as $row) {
  PhabricatorSystemActionEngine::willTakeAction(
    array($actor_phid), new ExampleAction(), 1.0);
  do_work($row);
}

// after: back off and retry when the actor is throttled
foreach ($rows as $row) {
  while (true) {
    try {
      PhabricatorSystemActionEngine::willTakeAction(
        array($actor_phid), new ExampleAction(), 1.0);
      break;
    } catch (PhabricatorSystemActionRateLimitException $ex) {
      sleep(60); // wait for scores to decay, then retry this row
    }
  }
  do_work($row);
}
Defensive patterns

Strategy: retry

Try / catch

Wrap only the willTakeAction() call and catch PhabricatorSystemActionRateLimitException specifically (not Exception); read the exception's action/score, wait out the decay window with a sleep of tens of seconds, then retry the same item. Never widen the catch: real failures must still surface.

Prevention

When it happens

Trigger: Calling willTakeAction() with a positive score for an actor whose recent score for that action exceeds the configured limit: bulk mail/mention/token actions, account creation bursts, or any per-row scripted action whose thresholds (Config > Actions) are set low relative to script volume.

Common situations: Scripts iterating thousands of records and firing one rate-limited action per row; a lowered threshold in PhabricatorSystemActionConfig; a shared bot actor whose budget is consumed by normal traffic plus scripted traffic.

Related errors


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/f3a9b2de3d0e6371. Report an issue: GitHub.