So, it appears the issue was not with the worker but in the client. After some debugging I tried this instead:
function handleQ(data)
disp('I ran!')
%rethrow(data)
end
In this case the code indicates it ran, and we also see that the warning was coming from rethrow(data), not from the worker.
This indicates that the afterEach callback operates more like a timer than a graphics callback, since the latter would actually throw the error.
So in summary, if you see warnings when you think you should be getting an error, it is because of throwing an error in the client callback method, not a warning that is being somehow thrown in the worker. This then begs the question of how to throw an error in the client when the worker fails ...