Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions e2e/cli/test_bash_duplicate_trust_warning
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#!/usr/bin/env bash
# shellcheck disable=SC2016
set -euo pipefail

# Reproduces the case where entering an untrusted project triggers hook-env from
# both chpwd and PROMPT_COMMAND. The user-visible symptom is a duplicate `mise trust`
# warning after a single `cd`.
#
# The expected bash behavior is:
# - `_mise_hook_prompt_command` remains registered after activation and after cd
# - the prompt immediately following `cd` does not repeat the same trust warning
# - bash does not fall back to chpwd-only behavior after the startup prompt

# The e2e harness trusts the isolated workspace by default; clear that so this
# test exercises the real untrusted-config warning path.
export MISE_TRUSTED_CONFIG_PATHS=""

# In CI, mise auto-trusts configs via ci_info::is_ci(); clear the common CI vars
# so hook-env follows the normal interactive trust-check behavior.
unset CI GITHUB_ACTIONS GITHUB_ACTION 2>/dev/null || true

contains_trust_warning() {
local file="$1"
grep -q "Config files in" "$file" && grep -q "are not trusted" "$file"
}

contains_mise_prompt_hook() {
[[ ";${PROMPT_COMMAND:-};" == *";_mise_hook_prompt_command;"* ]]
}

mkdir -p project
cat >project/.mise.toml <<'EOF'
[env]
FOO = "bar"
EOF

eval "$(mise activate bash --status)"

startup_file="$(mktemp)"
chpwd_file="$(mktemp)"
precmd_file="$(mktemp)"
trap 'rm -f "$startup_file" "$chpwd_file" "$precmd_file"' EXIT

# A persistent mise PROMPT_COMMAND hook should be registered immediately after activation.
if ! contains_mise_prompt_hook; then
echo "expected mise PROMPT_COMMAND hook to be registered after activation"
echo "PROMPT_COMMAND: ${PROMPT_COMMAND:-}"
exit 1
fi

# The bash chpwd skip flag should be initialised to 0 after activation.
if [[ ${__MISE_BASH_CHPWD_RAN:-unset} != "0" ]]; then
echo "expected __MISE_BASH_CHPWD_RAN=0 after activation"
exit 1
fi

# Simulate the first prompt after activation before entering the untrusted project.
_mise_hook_prompt_command >"$startup_file" 2>&1 || true
if contains_trust_warning "$startup_file"; then
echo "startup PROMPT_COMMAND should not hit an untrusted project before cd"
cat "$startup_file"
exit 1
fi

# The chpwd skip flag must not have been touched.
if [[ ${__MISE_BASH_CHPWD_RAN:-unset} != "0" ]]; then
echo "expected no chpwd skip flag after the first prompt"
exit 1
fi

# The mise PROMPT_COMMAND hook should remain registered after the startup prompt
# so bash can continue checking for later environment changes on subsequent prompts.
if ! contains_mise_prompt_hook; then
echo "expected mise PROMPT_COMMAND hook to remain registered after the startup prompt"
echo "PROMPT_COMMAND: ${PROMPT_COMMAND:-}"
exit 1
fi

# Use builtin cd to avoid __zsh_like_cd triggering chpwd_functions automatically,
# so we can capture the hook output separately below.
builtin cd project

# Fire chpwd hooks manually (simulating what __zsh_like_cd does after cd).
: >"$chpwd_file"
for _hook in "${chpwd_functions[@]-}"; do
"$_hook" >>"$chpwd_file" 2>&1 || true
done

# After chpwd, the skip flag should be set.
if [[ ${__MISE_BASH_CHPWD_RAN:-unset} != "1" ]]; then
echo "expected __MISE_BASH_CHPWD_RAN=1 after chpwd"
exit 1
fi
Comment thread
greptile-apps[bot] marked this conversation as resolved.

# The chpwd hook should surface the untrusted-config warning on directory entry.
if ! contains_trust_warning "$chpwd_file"; then
echo "expected chpwd hook to report untrusted config"
cat "$chpwd_file"
exit 1
fi

# Fire the PROMPT_COMMAND hook that would run at the next prompt after cd.
_mise_hook_prompt_command >"$precmd_file" 2>&1 || true

# The PROMPT_COMMAND hook should have consumed the chpwd skip flag.
if [[ ${__MISE_BASH_CHPWD_RAN:-unset} != "0" ]]; then
echo "expected __MISE_BASH_CHPWD_RAN to be reset to 0 after precmd consumed it"
exit 1
fi

# The subsequent prompt should not repeat the same warning.
if contains_trust_warning "$precmd_file"; then
echo "PROMPT_COMMAND hook should not repeat untrusted config warning after chpwd"
cat "$precmd_file"
exit 1
fi

# The mise PROMPT_COMMAND hook should still remain registered after chpwd + precmd
# so later prompts can continue to notice config/watch-file changes.
if ! contains_mise_prompt_hook; then
echo "expected mise PROMPT_COMMAND hook to remain registered after chpwd + precmd"
echo "PROMPT_COMMAND: ${PROMPT_COMMAND:-}"
exit 1
fi
27 changes: 27 additions & 0 deletions e2e/env/test_bash_consecutive_prompt_runs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -euo pipefail

# Verify that bash does not fall into an every-other-prompt pattern after the
# first PROMPT_COMMAND run. Two consecutive prompt runs without a directory
# change should both run hook-env normally.

eval "$(mise activate bash --status)"

if [[ ${__MISE_BASH_CHPWD_RAN:-unset} != "0" ]]; then
echo "expected __MISE_BASH_CHPWD_RAN=0 after activation"
exit 1
fi

_mise_hook_prompt_command >/dev/null 2>&1 || true

if [[ ${__MISE_BASH_CHPWD_RAN:-unset} != "0" ]]; then
echo "expected no chpwd skip flag after the first prompt"
exit 1
fi

_mise_hook_prompt_command >/dev/null 2>&1 || true

if [[ ${__MISE_BASH_CHPWD_RAN:-unset} != "0" ]]; then
echo "expected no chpwd skip flag after the second prompt"
exit 1
fi
21 changes: 21 additions & 0 deletions e2e/env/test_bash_deactivate_clears_chpwd_hook
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail

# Verify that bash deactivation removes the stale `_mise_hook_chpwd` entry from
# `chpwd_functions` as well as unsetting the function itself.

eval "$(mise activate bash --status)"

if [[ " ${chpwd_functions[*]-} " != *" _mise_hook_chpwd "* ]]; then
echo "expected _mise_hook_chpwd to be registered after activation"
printf '%s\n' "${chpwd_functions[@]-}"
exit 1
fi

mise deactivate

if [[ " ${chpwd_functions[*]-} " == *" _mise_hook_chpwd "* ]]; then
echo "expected _mise_hook_chpwd to be removed from chpwd_functions after deactivation"
printf '%s\n' "${chpwd_functions[@]-}"
exit 1
fi
20 changes: 20 additions & 0 deletions e2e/env/test_bash_first_prompt_after_activate
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -euo pipefail

# Verify that the first PROMPT_COMMAND run after `mise activate bash` still runs
# hook-env. This protects the startup path where bash may mutate PATH during
# shell initialization before the first prompt is rendered.

eval "$(mise activate bash --status)"

if [[ ${__MISE_BASH_CHPWD_RAN:-unset} != "0" ]]; then
echo "expected __MISE_BASH_CHPWD_RAN=0 after activation"
exit 1
fi

_mise_hook_prompt_command >/dev/null 2>&1 || true

if [[ ${__MISE_BASH_CHPWD_RAN:-unset} != "0" ]]; then
echo "expected first prompt after activation to leave __MISE_BASH_CHPWD_RAN unchanged"
exit 1
fi
77 changes: 77 additions & 0 deletions src/assets/bash/activate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# shellcheck shell=bash
__MISE_EXE=__MISE_EXE_VALUE__
__MISE_FLAGS=(__MISE_FLAGS_VALUE__)
__MISE_HOOK_ENABLED=__MISE_HOOK_ENABLED_VALUE__

export MISE_SHELL=bash

# On first activation, save the original PATH
# On re-activation, we keep the saved original
if [ -z "${__MISE_ORIG_PATH:-}" ]; then
export __MISE_ORIG_PATH="$PATH"
fi
__MISE_BASH_CHPWD_RAN=0

mise() {
local command
command="${1:-}"
if [ "$#" = 0 ]; then
command "$__MISE_EXE"
return
fi
shift

case "$command" in
deactivate | shell | sh)
# if argv doesn't contains -h,--help
if [[ ! " $* " =~ " --help " ]] && [[ ! " $* " =~ " -h " ]]; then
eval "$(command "$__MISE_EXE" "$command" "$@")"
return $?
fi
;;
esac
command "$__MISE_EXE" "$command" "$@"
}

_mise_hook() {
local previous_exit_status=$?
eval "$(mise hook-env "${__MISE_FLAGS[@]}" -s bash)"
return $previous_exit_status
}

if [ "$__MISE_HOOK_ENABLED" = "1" ]; then
_mise_hook_prompt_command() {
local previous_exit_status=$?
if [[ ${__MISE_BASH_CHPWD_RAN:-0} == "1" ]]; then
__MISE_BASH_CHPWD_RAN=0
return $previous_exit_status
fi
eval "$(mise hook-env "${__MISE_FLAGS[@]}" -s bash --reason precmd)"
return $previous_exit_status
}

_mise_hook_chpwd() {
local previous_exit_status=$?
__MISE_BASH_CHPWD_RAN=1
eval "$(mise hook-env "${__MISE_FLAGS[@]}" -s bash --reason chpwd)"
return $previous_exit_status
}

_mise_add_prompt_command() {
if [[ "$(declare -p PROMPT_COMMAND 2>/dev/null)" == "declare -a"* ]]; then
if [[ " ${PROMPT_COMMAND[*]} " != *" _mise_hook_prompt_command "* ]]; then
PROMPT_COMMAND=("_mise_hook_prompt_command" "${PROMPT_COMMAND[@]}")
fi
elif [[ ";${PROMPT_COMMAND:-};" != *";_mise_hook_prompt_command;"* ]]; then
local _mise_prompt_command_value="${PROMPT_COMMAND-}"
printf -v PROMPT_COMMAND '%s' "_mise_hook_prompt_command${_mise_prompt_command_value:+;$_mise_prompt_command_value}"
fi
}

_mise_add_prompt_command
__MISE_CHPWD_FUNCTIONS__
__MISE_CHPWD_LOAD__
chpwd_functions+=(_mise_hook_chpwd)
Comment thread
timothysparg marked this conversation as resolved.
fi

_mise_hook

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To avoid a duplicate trust warning on shell startup when activating inside an untrusted project, the initial _mise_hook call should also set the __MISE_BASH_CHPWD_RAN flag. This ensures that the first PROMPT_COMMAND run immediately following activation will skip its redundant environment check.

Suggested change
_mise_hook
_mise_hook && __MISE_BASH_CHPWD_RAN=1

20 changes: 20 additions & 0 deletions src/assets/bash/command_not_found.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# shellcheck shell=bash
if [ -z "${_mise_cmd_not_found:-}" ]; then
_mise_cmd_not_found=1
if [ -n "$(declare -f command_not_found_handle)" ]; then
_mise_cmd_not_found_handle=$(declare -f command_not_found_handle)
eval "${_mise_cmd_not_found_handle/command_not_found_handle/_command_not_found_handle}"
fi

command_not_found_handle() {
if [[ $1 != "mise" && $1 != "mise-"* ]] && __MISE_EXE__ hook-not-found -s bash -- "$1"; then
_mise_hook
"$@"
elif [ -n "$(declare -f _command_not_found_handle)" ]; then
_command_not_found_handle "$@"
else
echo "bash: command not found: $1" >&2
return 127
fi
}
fi
57 changes: 57 additions & 0 deletions src/assets/bash/deactivate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# shellcheck shell=bash
if [[ "$(declare -p PROMPT_COMMAND 2>/dev/null)" == "declare -a"* ]]; then
_mise_prompt_command=()
for _mise_pc in "${PROMPT_COMMAND[@]}"; do
if [[ $_mise_pc != "_mise_hook_prompt_command" && $_mise_pc != "_mise_hook" ]]; then
_mise_prompt_command+=("$_mise_pc")
fi
done
Comment thread
timothysparg marked this conversation as resolved.
PROMPT_COMMAND=("${_mise_prompt_command[@]}")
unset _mise_prompt_command _mise_pc
elif [[ ${PROMPT_COMMAND-} == *_mise_hook_prompt_command* ]]; then
_mise_prompt_command_value="${PROMPT_COMMAND-}"
_mise_prompt_command_value="${_mise_prompt_command_value//_mise_hook_prompt_command;/}"
_mise_prompt_command_value="${_mise_prompt_command_value//;_mise_hook_prompt_command/}"
_mise_prompt_command_value="${_mise_prompt_command_value//_mise_hook_prompt_command/}"
printf -v PROMPT_COMMAND '%s' "$_mise_prompt_command_value"
unset _mise_prompt_command_value
elif [[ ${PROMPT_COMMAND-} == *_mise_hook* ]]; then
_mise_prompt_command_value="${PROMPT_COMMAND-}"
_mise_prompt_command_value="${_mise_prompt_command_value//_mise_hook;/}"
_mise_prompt_command_value="${_mise_prompt_command_value//;_mise_hook/}"
_mise_prompt_command_value="${_mise_prompt_command_value//_mise_hook/}"
Comment thread
timothysparg marked this conversation as resolved.
printf -v PROMPT_COMMAND '%s' "$_mise_prompt_command_value"
unset _mise_prompt_command_value
fi

if declare -p chpwd_functions >/dev/null 2>&1; then
_mise_chpwd_functions=()
for _mise_f in "${chpwd_functions[@]}"; do
if [[ $_mise_f != "_mise_hook_chpwd" && $_mise_f != "_mise_hook" ]]; then
_mise_chpwd_functions+=("$_mise_f")
fi
done
Comment thread
timothysparg marked this conversation as resolved.
chpwd_functions=("${_mise_chpwd_functions[@]}")
unset _mise_chpwd_functions _mise_f
fi

declare -F _mise_hook_prompt_command >/dev/null && unset -f _mise_hook_prompt_command
declare -F _mise_add_prompt_command >/dev/null && unset -f _mise_add_prompt_command
declare -F _mise_hook_chpwd >/dev/null && unset -f _mise_hook_chpwd
declare -F _mise_hook >/dev/null && unset -f _mise_hook
if [ -n "${_mise_cmd_not_found_handle:-}" ]; then
eval "$_mise_cmd_not_found_handle"
unset _mise_cmd_not_found_handle
declare -F _command_not_found_handle >/dev/null && unset -f _command_not_found_handle
elif [[ "$(declare -f command_not_found_handle 2>/dev/null)" == *"hook-not-found"* ]]; then
declare -F command_not_found_handle >/dev/null && unset -f command_not_found_handle
fi
declare -F mise >/dev/null && unset -f mise
Comment thread
timothysparg marked this conversation as resolved.
unset MISE_SHELL
unset __MISE_DIFF
unset __MISE_SESSION
unset __MISE_EXE
unset __MISE_FLAGS
unset __MISE_HOOK_ENABLED
unset __MISE_BASH_CHPWD_RAN
Comment thread
timothysparg marked this conversation as resolved.
unset _mise_cmd_not_found
Loading
Loading