“Sticky” posts for each category (archive.php)

To make this more complete, here is what I have said in comments in reply to the question on hand

Just to quickly explain, WP_Query fails catastrophically in some cases where empty arrays are passed to some of its parameters, instead of also returning an empty array as we should expect, WP_Query returns all posts. As for getting the correct stickies, as I said before, you need to get the current category id and use that to filter the sticky posts. Remember, with your approach, you need remove sticky posts from the main query otherwise you will get duplicates

As an alternative solution using hooks and filters on the main query, and working from a similar question/answer, this is what I have come up with: (Code is well commented so it can be followed. CAVEAT: This is untested though, and needs at least PHP 5.4+)

function get_term_sticky_posts()
{
    // First check if we are on a category page, if not, return false
    if ( !is_category() )
        return false;

    // Secondly, check if we have stickies, return false on failure
    $stickies = get_option( 'sticky_posts' );

    if ( !$stickies )
        return false;

    // OK, we have stickies and we are on a category page, continue to execute. Get current object (category) ID
    $current_object = get_queried_object_id();

    // Create the query to get category specific stickies, just get post ID's though
    $args = [
        'nopaging' => true,
        'post__in' => $stickies,
        'cat' => $current_object,
        'ignore_sticky_posts' => 1,
        'fields' => 'ids'
    ];
    $q = get_posts( $args );

    return $q;
}

add_action( 'pre_get_posts', function ( $q )
{
    if (    !is_admin() // IMPORTANT, make sure to target front end only
         && $q->is_main_query() // IMPORTANT, make sure we only target the main query
         && $q->is_category() // Only target category archives
    ) {
        // Check if our function to get term related stickies exists to avoid fatal errors
        if ( function_exists( 'get_term_sticky_posts' ) ) {
            // check if we have stickies
            $stickies = get_term_sticky_posts();

            if ( $stickies ) {
                // Remove stickies from the main query to avoid duplicates
                $q->set( 'post__not_in', $stickies );

                // Check that we add stickies on the first page only, remove this check if you need stickies on all paged pages
                if ( !$q->is_paged() ) {

                    // Add stickies via the the_posts filter
                    add_filter( 'the_posts', function ( $posts ) use ( $stickies )
                    {   
                        $term_stickies = get_posts( ['post__in' => $stickies, 'nopaging' => true] );

                        $posts = array_merge( $term_stickies, $posts );

                        return $posts;
                    }, 10, 1 );
                }
            }
        }
    }
});

FEW NOTES:

  • This only works with default category taxonomy. The code can be easily modified (please do that, modify to your needs) to use any taxonomy and its relevant terms

  • You just add this to functions.php. No need to alter your template files or using custom queries. All you need is the main query with default loop

EDIT

The above code is now tested and working on WordPress 4.2.1 and PHP 5.4+