phacility/phabricator · error · Exception

Call %s when rendering an action as a form.

Error message

Call %s when rendering an action as a form.

What it means

When a PhabricatorActionView renders as a form (setRenderAsForm(true) with an href), it emits a real form whose CSRF/session token derives from the viewer, so render() requires one and throws if none is set. PhabricatorActionListView propagates its own viewer to every action at render time (it calls $action->setViewer($viewer)), so actions inside a viewer-configured list never hit this; standalone actions or viewer-less lists do.

Source

Thrown at src/view/layout/PhabricatorActionView.php:234

    if ($this->download) {
      $sigils[] = 'download';
    }

    if ($this->submenu) {
      $sigils[] = 'keep-open';
    }

    if ($this->sigils) {
      $sigils = array_merge($sigils, $this->sigils);
    }

    $sigils = $sigils ? implode(' ', $sigils) : null;

    if ($this->href) {
      if ($this->renderAsForm) {
        if (!$this->hasViewer()) {
          throw new Exception(
            pht(
              'Call %s when rendering an action as a form.',
              'setViewer()'));
        }

        $item = javelin_tag(
          'button',
          array(
            'class' => 'phabricator-action-view-item',
          ),
          array($icon, $this->name));

        $item = phabricator_form(
          $this->getViewer(),
          array(
            'action'    => $this->getHref(),
            'method'    => 'POST',
            'sigil'     => $sigils,

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Call $action_view->setViewer($viewer) directly on the action.
  2. Or attach actions to a PhabricatorActionListView and call $list->setViewer($viewer) once; the list propagates it to each action at render time.
  3. In controllers, always source the viewer from $this->getViewer().

Example fix

// before
$action = id(new PhabricatorActionView())
  ->setHref('/obj/subscribe/')
  ->setRenderAsForm(true)
  ->setName(pht('Subscribe'));
// no viewer: render() throws

// after
$action = id(new PhabricatorActionView())
  ->setViewer($viewer)
  ->setHref('/obj/subscribe/')
  ->setRenderAsForm(true)
  ->setName(pht('Subscribe'));
Defensive patterns

Strategy: validation

Validate before calling

if (!$action->hasViewer()) {
  $action->setViewer($viewer);
}
// and for lists (propagates to every action on render):
$list->setViewer($viewer);

Prevention

When it happens

Trigger: Creating a standalone PhabricatorActionView with setRenderAsForm(true) (and an href) and rendering it without setViewer(); or rendering an action list on which setViewer() was never called.

Common situations: Custom action lists built in controllers where $list->setViewer($viewer) was forgotten; embedding a single form-action in a hovercard or curtain; refactors that pull an action out of a list into standalone rendering.

Related errors


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