Custom Category Walker with Image, Fallback to Most Recent Post in Category Image

The solution to the problem is that the tax_query needs to be an array of arrays. Thank you Sally CJ for the comment above, which also reminded me to $query->request.

Here’s the total Walker for anyone for whom it’s useful:

class List_Category_Walker extends Walker_Category {
    function start_el( &$output, $category, $depth = 0, $args = array(), $id = 0 ) {

        $cat_name = apply_filters(
            'list_cats',
            esc_attr( $category->name ),
            $category
        );

        $link = '<a href="' . esc_url( get_term_link( $category ) ) . '" ';
        $link .= '>';

        // Get array of images from ACF Image field
        // Get array of images from ACF Image field
        if function_exists('get_field'):
          $image_array = get_field('main_category_image', 'term_' . $category->term_id);
        else:
          $image_array = [];
        endif;

        // But if that's not set set it to image from latest post within category
        if (empty($image_array['sizes']['medium'])):
          // Get one post
          $args = array(
            'post_type' => 'project',
            'tax_query' => array([
                'taxonomy' => 'project-category',
                'terms' => $category->term_id,
            ]),
            'posts_per_page' => 1,
            'orderby' => 'date',
            'order' => 'ASC'
          );

          global $post;

          $query = new WP_Query($args);

          if ( $query->have_posts() ) {
            while ( $query->have_posts() ) {

              $query->the_post();

              $image = get_the_post_thumbnail_url( $post->ID, 'medium', null );

            }

          wp_reset_postdata();

          } else {

            // some default image

          }

        else:
          $image = $image_array['sizes']['medium'];
        endif;

        $link .= '<img src="' . $image . '" alt="'. $category->name .'">';
        $link .= "<br/>" . $cat_name . '</a>';

        if ( ! empty( $args['show_count'] ) ) {
          $link .= ' (' . number_format_i18n( $category->count ) . ')';
        }
        $output .= "\t<li";
        $class="cat-item cat-item-" . $category->term_id;
        if ( ! empty( $args['current_category'] ) ) {
            $_current_category = get_term( $args['current_category'], $category->taxonomy );
            if ( $category->term_id == $args['current_category'] ) {
                $class .=  ' current-cat';
            } elseif ( $category->term_id == $_current_category->parent ) {
                $class .=  ' current-cat-parent';
            }
        }
        $output .=  ' class="' . $class . '"';
        $output .= ">$link\n";
    }
}