Turns out the problem I ran into was a clash between dirtrack-mode and shell-dirtrack-mode. I had no idea there were two competing packages; the problem was that dirtrack picked up the new path, then shell-dirtrack noticed the “cd ..”.
Solved:
(add-hook 'shell-mode-hook
(lambda ()
(setq shell-dirtrackp nil)
(add-hook 'comint-preoutput-filter-functions 'dirtrack nil t)))
This caused some trouble with ssh-mode. Solved thusly:
(add-hook 'ssh-mode-hook (lambda () (setq dirtrackp nil)))
Discussion