If a bash function does cd somewhere and returns without cd - back, the caller is now in a different directory. This is a nightmare in Makefiles and deploy scripts where the same shell runs several build steps. pushd and popd make a stack out of the CWD so nesting is safe.

build_component() {
    local dir="$1"
    pushd "$dir" >/dev/null
    trap 'popd >/dev/null' RETURN

    make clean
    make build
    make test
}

build_component ./services/api
build_component ./services/worker
# CWD is wherever the caller started.

trap ... RETURN runs on function return (bash only, not POSIX sh). It fires even if set -e aborts the function mid-way, which is the thing you actually want: the caller never sees a moved CWD.

If you cannot use bash, the portable form is:

build_component() {
    (
        cd "$1" || exit 1
        make clean
        make build
        make test
    )
}

The subshell gives you an isolated CWD for free: the parent never leaves its original directory. Slightly more expensive (fork) but bullet-proof across shells.

See also /snippets/bash-trap-cleanup-tmpdir/.