why is checkout -f the right thing to put in a post-receive hook?

Solution:

post-receive is a server-side hook which will be executed on git push.

So no need for a pull: the client pushes some content, which is then checked out on the server.

Why “checkout -f”?

First, “checkout -f” is actually checkout -f @, or checkout -f HEAD: it checks out whatever HEAD is now (after the push).

Second, the --force option ensures that switching branch succeeds even if the index or working tree differs from HEAD (which it will, since the push just changed said HEAD)

But I would try instead

GIT_WORK_TREE=/home/user/theme-directory git checkout -f  -- .

That is: specifying a pathspec, which will overwrite paths in the working tree by replacing with the contents in the index or in the (most often a commit, here: HEAD, which just changed after the push).