I realized that my issue was because of conflicting tweens. In the closing function, I was changing the opacity of the card being flipped before it had a chance to finish it's animation. I've updated the function like this.
function closeHeadshot(){
document.removeEventListener('click', closeHeadshot);
//Record current state
const state = Flip.getState(expandedContainer);
//Scale details down so that it's image fits exactly on top of the active headshot
Flip.fit(expandedContainer, activeHeadshot.querySelector('img'), {scale: true, fitChild: expandedImage});
//Put the bio container back and then fade in the other headshots
const tl = gsap.timeline();
tl.to(expandedBioContainer, {xPercent: 15, opacity: 0})
//.to(expandedContainer, {visibility: 'hidden'})
.to(headshots, {opacity:1, stagger: {amount: 0.7, from: headshots.indexOf(activeHeadshot), grid: 'auto'}}, 0);
Flip.from(state, {
scale: true,
duration: .5,
delay: 0.2,
ease: "power2.inOut",
absolute: true
}).to(expandedContainer, {visibility: 'hidden'})
activeHeadshot = null;
}