Our bug was views-view-field.html.twig coming back empty for the entity API field track_icon on the node 80136.
The template only prints the output variable.
Where is that coming from?

function template_preprocess_views_view_field(&$variables) {
  $variables['output'] = $variables['field']->advancedRender($variables['row']);
}

In FieldPluginBase::advancedRender I set a breakpoint

$values->_entity->id() == 80136 && $this->table == 'taxonomy_term__track_icon'

we hit my breakpoint exactly twice as expected because we print this node twice
the second time $raw_items = $this->getItems($values); comes back empty (not good!)
EntityField::getItems() runs $build_list = $this->getEntityFieldRenderer()->render($values, $this);
in turn EntityFieldRenderer::render runs into Pick the render array for the row / field we are being asked to render, and remove it from $this->build to free memory as we progress.
the meat is $build = $this->build[$row->index][$field_id];
now, $build when broken only contains cache metadata, nothing else. Just a #cache key, with context and tags.
Paging through the class, it’s not a lot of code, we find the buildFields method which iterates every row in the result and calls EntityViewDisplay to run formatters:

$display_build = $display->buildMultiple($bundle_entities);  

EntityViewDisplay::buildMultiple has this gem

$build_list[$id][$name] = $field_access->isAllowed() ? $formatter->view($items, $view_langcode) : [];  
// Apply the field access cacheability metadata to the render array.  
$this->renderer->addCacheableDependency($build_list[$id][$name], $field_access);  

that is beyond suspicious
because that’s where the nothing but cacheable metadata” might very well come from
so we slap a $name === 'track_icon' && !$build_list[$id][$name] breakpoint here (we know $name is the field name because foreach ($this->getComponents() as $name => $options) {
doesnt fire
darn
that was a good shot
let’s try $name === 'track_icon' && !isset($build_list[$id][$name]['#theme'])
that fires the expected amount of times, yay
so i set a breakpoint on

$build_list[$id][$name] = $field_access->isAllowed() ? $formatter->view($items, $view_langcode) : [];

itself for name track_icon and id 1 and step through
bingo
we ran afoul of

if (static::$recursiveRenderDepth[$recursive_render_id] > static::RECURSIVE_RENDER_LIMIT) {  

in EntityReferenceEntityFormatter::viewElements
The recursion protection is only increased ever, never decreased.
Despite the name of the constant RECURSIVE_RENDER_LIMIT the doxygen accurately tells you it actually has nothing to do with recursion and this is a feature not a bug:

The number of times this formatter allows rendering the same entity.

I will, for now, live without this feature.

August 28, 2020


Previous post
Writing the tranc module We hit Content and interface translation don’t clearly separate. I set out to fix it for ourselves and then released it back. It’s possible the
Next post
There are two modules providing lat/lon storage in Drupal 8/8: geolocation and geofield. I went with geofield simply because geocluster is using it