Brian, the reason it stops the whole run instead of skipping one user is in how GAM handles the error. For print othercontacts, GAM only treats serviceNotAvailable, forbidden, permissionDenied and failedPrecondition as per-user errors (PEOPLE_ACCESS_THROW_REASONS in gamlib/glgapi.py). A 401 authError isn't on that list, so it ends the command. The Drive and Calendar commands do include authError and just warn and move on, which is likely why everything else works for you.
So the run dying is GAM's handling. Why that one user got a 401 I can't tell from here. Run the account it stopped on by itself (the last user GAM printed before the error):
gam user
use...@yourdomain.com print othercontacts emailmatchpattern "
jo...@example.com"
If that gives the 401 again, it's something about that account. If it works, the 401 was likely timing on the long run rather than the account.
To get through all 2000 regardless, run each user in its own process so one failure doesn't stop the rest (the pattern from the Bulk Processing wiki page):
gam config auto_batch_min 1 redirect csv ./othercontacts.csv multiprocess all users print othercontacts emailmatchpattern "
jo...@example.com"