I did get the thing working, though I’m not entirely sure how.
I made two changes:
Removed the “convert array to Tax_Query object” step (which I only put in because using an array wasn’t working before)
Added a direct assignment of the array to the $query object (rather than just using the set() method).
My final (working) function is:
function pre_get_posts( $query ) {
if ( !is_admin() && $query->is_search ) {
if (isset($_GET['post_types'])) {
$query->set( 'post_type', $_GET['post_types']);
} else {
$query->set( 'post_type', array( 'post', 'resource', 'initiative') );
}
if (isset($_GET['tags'])) {
$tax_query = array(
array(
'taxonomy' => 'post_tag',
'field' => 'slug',
'terms' => $_GET['tags'],
'operator' => 'AND',
)
);
$query->set('tax_query', $tax_query); // do I need both of these lines?
$query->tax_query = $tax_query; // let's leave them both in for safety
}
}
return $query;
}