By default, ZSH hook chpwd is not triggered on shell startup. The tutorial here provides some ideas to fix this.
We can use a trick to define a function run only once on precmd and destruct itself automatically.
function _self_destruct_hook {
  local f
  for f in ${chpwd_functions}; do
    "$f"
  done
  # remove self from precmd
  precmd_functions=(${(@)precmd_functions:#_self_destruct_hook})
  builtin unfunction _self_destruct_hook
}
# prepend the hook, in case you want run the hook 1st
precmd_functions=(_self_destruct_hook ${precmd_functions[@]})
# or append the hook
(( $+functions[add-zsh-hook] )) || autoload -Uz add-zsh-hook
add-zsh-hook precmd _self_destruct_hookThe _self_destruct_hook wraps chpwd_functions and run every item within once on precmd/startup.
Unlike the method above, the following change makes it possible to run specific item from chpwd_functions.
# define an array to collect functions run only once
typeset -ag self_destruct_functions=()
function _self_destruct_hook {
  local f
  for f in ${self_destruct_functions}; do
    "$f"
  done
  # remove self from precmd
  precmd_functions=(${(@)precmd_functions:#_self_destruct_hook})
  builtin unfunction _self_destruct_hook
  unset self_destruct_functions
}
precmd_functions=(_self_destruct_hook ${precmd_functions[@]})
# example 1: hook direnv on chpwd and run it on startup/precmd once
if (( $+commands[direnv] )) && ! (( $+functions[_direnv_hook] )); then
  _direnv_hook() {
    eval "$(command "direnv" export zsh)";
  }
  typeset -ag chpwd_functions
  chpwd_functions=(_direnv_hook ${chpwd_functions[@]})
  # add the _direnv_hook into custom _self_destruct_hook
  # prepend _direnv_hook cause we need env var changes before any other action
  self_destruct_functions=(_direnv_hook ${self_destruct_functions[@]})
fi
# example 2: dynamic umask with direnv
function _umask_hook {
  if [[ -n $UMASK ]]; then
    umask "$UMASK"
  elif [[ $OSTYPE == darwin* ]]; then
    umask 0077
  else
    umask 0022
  fi
}
add-zsh-hook chpwd _umask_hook
# run _umask_hook once on shell startup
# append _umask_hook to make sure env var change made by direnv is triggered 1st
self_destruct_functions=(${self_destruct_functions[@]} _umask_hook)The self-destruct function is borrowed from robobenklein/zinc the ZSH prompt.