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
22 changes: 22 additions & 0 deletions bridges/_dispatch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,28 @@ _ensure_systemd_path_contains() {
' <<< "$current_env"
}

# _ensure_systemd_path_first <current_env> <preferred_dir>
# Remove duplicate occurrences and make the managed directory the first PATH
# entry. Unlike _ensure_systemd_path_contains, precedence matters for binaries
# that also exist in legacy per-user bin directories.
_ensure_systemd_path_first() {
local current_env="$1" preferred_dir="$2"
[ -n "$preferred_dir" ] || { printf '%s\n' "$current_env"; return 0; }
awk -v dir="$preferred_dir" '
/^Environment=PATH=/ {
value = substr($0, length("Environment=PATH=") + 1)
count = split(value, entries, ":")
path = dir
for (i = 1; i <= count; i++) {
if (entries[i] != "" && entries[i] != dir) path = path ":" entries[i]
}
print "Environment=PATH=" path
next
}
{ print }
' <<< "$current_env"
}

# _systemd_unit_user <unit_file>
#
# Print the User= value from an existing systemd unit. Empty output +
Expand Down
19 changes: 15 additions & 4 deletions bridges/kimaki.sh
Original file line number Diff line number Diff line change
Expand Up @@ -928,10 +928,15 @@ _kimaki_install_systemd() {

KIMAKI_BIN=$(_kimaki_resolve_service_bin "/usr/bin/kimaki")

local KIMAKI_BIN_DIR NODE_BIN_DIR PATH_VALUE
local KIMAKI_BIN_DIR NODE_BIN_DIR HOMEBOY_BIN_DIR PATH_VALUE
KIMAKI_BIN_DIR=$(dirname "$KIMAKI_BIN")
NODE_BIN_DIR=$(_resolve_node_bin_dir "$KIMAKI_BIN")
PATH_VALUE=$(_compose_path_value "$KIMAKI_BIN_DIR" "$NODE_BIN_DIR" /usr/local/bin /usr/bin /bin)
HOMEBOY_BIN_DIR=""
if [ "${LOCAL_MODE:-false}" != true ] && [ "${EXTERNAL_WORDPRESS:-false}" != true ] \
&& [ -n "${SERVICE_USER:-}" ] && [ "$SERVICE_USER" != root ]; then
HOMEBOY_BIN_DIR="$(dirname "${WP_CODING_AGENTS_HOMEBOY_MANAGED_BIN:-/usr/local/lib/wp-coding-agents/bin/homeboy}")"
fi
PATH_VALUE=$(_compose_path_value "$HOMEBOY_BIN_DIR" "$KIMAKI_BIN_DIR" "$NODE_BIN_DIR" /usr/local/bin /usr/bin /bin)
_kimaki_assert_bin_identity "$KIMAKI_BIN" "$PATH_VALUE"
# Kimaki recreates a general-purpose #kimaki-<bot> channel, welcome message,
# and tutorial thread on every start. On a wp-coding-agents install the real
Expand Down Expand Up @@ -1310,11 +1315,17 @@ bridge_update_systemd() {
local KIMAKI_BIN
KIMAKI_BIN=$(_kimaki_resolve_service_bin "/usr/bin/kimaki")
local KIMAKI_CONFIG_DIR="/opt/kimaki-config"
local KIMAKI_BIN_DIR NODE_BIN_DIR PATH_VALUE
local KIMAKI_BIN_DIR NODE_BIN_DIR HOMEBOY_BIN_DIR PATH_VALUE
KIMAKI_BIN_DIR=$(dirname "$KIMAKI_BIN")
NODE_BIN_DIR=$(_resolve_node_bin_dir "$KIMAKI_BIN")
PATH_VALUE=$(_compose_path_value "$KIMAKI_BIN_DIR" "$NODE_BIN_DIR" /usr/local/bin /usr/bin /bin)
HOMEBOY_BIN_DIR=""
if [ "${LOCAL_MODE:-false}" != true ] && [ "${EXTERNAL_WORDPRESS:-false}" != true ] \
&& [ -n "${SERVICE_USER:-}" ] && [ "$SERVICE_USER" != root ]; then
HOMEBOY_BIN_DIR="$(dirname "${WP_CODING_AGENTS_HOMEBOY_MANAGED_BIN:-/usr/local/lib/wp-coding-agents/bin/homeboy}")"
fi
PATH_VALUE=$(_compose_path_value "$HOMEBOY_BIN_DIR" "$KIMAKI_BIN_DIR" "$NODE_BIN_DIR" /usr/local/bin /usr/bin /bin)
_kimaki_assert_bin_identity "$KIMAKI_BIN" "$PATH_VALUE"
CURRENT_ENV=$(_ensure_systemd_path_first "$CURRENT_ENV" "$HOMEBOY_BIN_DIR")
CURRENT_ENV=$(_ensure_systemd_path_contains "$CURRENT_ENV" "$KIMAKI_BIN_DIR")
if [ -n "$NODE_BIN_DIR" ]; then
CURRENT_ENV=$(_ensure_systemd_path_contains "$CURRENT_ENV" "$NODE_BIN_DIR")
Expand Down
9 changes: 4 additions & 5 deletions guidance/homeboy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,23 @@ guidance_freshness() { printf 'live'; }
# Overridable so tests never touch the real path, and so a host whose
# homeboy-upgrade helper installs somewhere else can still be recognized.
_guidance_homeboy_managed_bin_path() {
printf '%s' "${WP_CODING_AGENTS_HOMEBOY_MANAGED_BIN:-/usr/local/bin/homeboy}"
printf '%s' "${WP_CODING_AGENTS_HOMEBOY_MANAGED_BIN:-/usr/local/lib/wp-coding-agents/bin/homeboy}"
}

_guidance_homeboy_service_bin_path() {
[ "${LOCAL_MODE:-false}" != true ] || return 0
[ "${EXTERNAL_WORDPRESS:-false}" != true ] || return 0
[ -n "${SERVICE_USER:-}" ] && [ "$SERVICE_USER" != root ] || return 0
[ -n "${SERVICE_HOME:-}" ] || return 0
printf '%s/.local/bin/homeboy' "$SERVICE_HOME"
_guidance_homeboy_managed_bin_path
}

# _guidance_homeboy_bin_candidates — ordered, de-duplicated candidate paths.
#
# 1. WP_CODING_AGENTS_HOMEBOY_BIN — explicit sync-time override, for an
# operator with a nonstandard install.
# 2. The managed system install location (see above): root-owned and
# world-executable by convention, stable across every identity on the
# box, unlike a per-user PATH entry.
# 2. The managed shared install location (see above), reachable to the
# composing web identity and writable by the service identity.
# 3. `type -P homeboy` — the syncing user's PATH (the original #575
# probe), kept as a last resort for hosts that run homeboy from
# somewhere else but still keep it web-reachable.
Expand Down
172 changes: 154 additions & 18 deletions lib/homeboy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,61 @@ homeboy_uses_service_owned_bin() {
}

homeboy_service_bin() {
printf '%s/.local/bin/homeboy' "$SERVICE_HOME"
printf '%s' "${WP_CODING_AGENTS_HOMEBOY_MANAGED_BIN:-/usr/local/lib/wp-coding-agents/bin/homeboy}"
}

# Provisioning may mutate only the dedicated wp-coding-agents prefix. In
# particular, a full-path override must never turn SERVICE_HOME/.local or an
# arbitrary operator-owned directory into a root-chowned prefix.
homeboy_path_has_no_symlink_components() {
local path="$1" current="" component
[[ "$path" = /* ]] || return 1
local -a components=()
IFS='/' read -r -a components <<< "${path#/}"
for component in "${components[@]}"; do
[ -n "$component" ] || continue
current="$current/$component"
[ ! -L "$current" ] || return 1
done
}

homeboy_managed_prefix_safe() {
local target="$1" service_home target_dir prefix parent owner mode mode_value
[[ "$target" = /* ]] || return 1
target="$(python3 -c 'import os,sys; print(os.path.abspath(sys.argv[1]))' "$target")" || return 1
homeboy_path_has_no_symlink_components "$target" || return 1
[ "$(basename "$target")" = homeboy ] || return 1
target_dir="$(dirname "$target")"
[ "$(basename "$target_dir")" = bin ] || return 1
prefix="$(dirname "$target_dir")"
[ "$(basename "$prefix")" = wp-coding-agents ] || return 1
[ ! -L "$target" ] && [ ! -L "$target_dir" ] && [ ! -L "$prefix" ] || return 1

service_home="${SERVICE_HOME:-}"
if [ -n "$service_home" ]; then
service_home="$(python3 -c 'import os,sys; print(os.path.abspath(sys.argv[1]))' "$service_home")" || return 1
case "$target" in "$service_home"/*) return 1 ;; esac
fi

parent="$(dirname "$prefix")"
[ -d "$parent" ] || return 1
owner="$(file_owner "$parent" 2>/dev/null)" || return 1
[ "$owner" = root ] || return 1
mode="$(file_mode "$parent" 2>/dev/null)" || return 1
mode_value="$(python3 -c 'import sys; print(int(sys.argv[1], 8))' "$mode" 2>/dev/null)" || return 1
if (( mode_value & 0022 )); then
# A root-owned sticky parent such as /tmp is safe for a root-created child;
# writable non-sticky parents are not trusted for managed prefix creation.
(( mode_value & 01000 )) || return 1
fi
if [ -e "$prefix" ]; then
[ -d "$prefix" ] || return 1
owner="$(file_owner "$prefix" 2>/dev/null)" || return 1
[ "$owner" = root ] || return 1
mode="$(file_mode "$prefix" 2>/dev/null)" || return 1
mode_value="$(python3 -c 'import sys; print(int(sys.argv[1], 8))' "$mode" 2>/dev/null)" || return 1
(( (mode_value & 0022) == 0 )) || return 1
fi
}

homeboy_system_bin() {
Expand All @@ -34,45 +88,127 @@ homeboy_system_bin() {
command -v homeboy 2>/dev/null || true
}

homeboy_service_bin_ready() {
local target target_dir target_owner dir_owner dir_mode dir_mode_value
target="$(homeboy_service_bin)"
target_dir="$(dirname "$target")"
[ -f "$target" ] && [ ! -L "$target" ] && [ -x "$target" ] || return 1
target_owner="$(file_owner "$target" 2>/dev/null)" || return 1
[ "$target_owner" = "$SERVICE_USER" ] || return 1
dir_owner="$(file_owner "$target_dir" 2>/dev/null)" || return 1
[ "$dir_owner" = "$SERVICE_USER" ] || return 1
dir_mode="$(file_mode "$target_dir" 2>/dev/null)" || return 1
dir_mode_value="$(python3 -c 'import sys; print(int(sys.argv[1], 8))' "$dir_mode" 2>/dev/null)" || return 1
(( (dir_mode_value & 0300) == 0300 ))
}

homeboy_bin() {
if homeboy_uses_service_owned_bin && [ -x "$(homeboy_service_bin)" ]; then
if homeboy_uses_service_owned_bin && homeboy_service_bin_ready; then
homeboy_service_bin
else
homeboy_system_bin
fi
}

homeboy_service_install_managed_binary() {
local target_dir="$1" target="$2" seed="$3" target_mode="$4" target_owner="$5"
local service_script='set -eu
directory=$1 target=$2 seed=$3 target_mode=$4
service_user=$5 target_owner=$6
chmod 0755 "$directory"
if [ -f "$target" ] && [ ! -L "$target" ] && [ -x "$target" ] && [ "$target_mode" = 755 ] && [ "$target_owner" = "$service_user" ]; then
exit 0
fi
source=$seed
if [ -f "$target" ] && [ ! -L "$target" ] && [ -x "$target" ] && [ "$target_owner" != "$service_user" ]; then
[ -r "$target" ] || { echo "Existing Homeboy is not readable by $service_user: $target" >&2; exit 1; }
source=$target
elif [ -f "$target" ] && [ ! -L "$target" ] && [ -x "$target" ] && [ -r "$target" ] && [ "$target_mode" != 755 ]; then
source=$target
fi
[ -n "$source" ] && [ -r "$source" ] || exit 0
temporary=$(mktemp "$directory/.homeboy.XXXXXX")
trap '\''rm -f "$temporary"'\'' EXIT
install -m 0755 "$source" "$temporary"
mv -f "$temporary" "$target"
'
local service_path="$target_dir:$PATH"

if [ "$(id -u)" -eq 0 ]; then
command -v sudo >/dev/null 2>&1 || error "Cannot converge Homeboy as '$SERVICE_USER': sudo is unavailable."
sudo -n -H -u "$SERVICE_USER" env HOME="$SERVICE_HOME" PATH="$service_path" \
/bin/bash -c "$service_script" homeboy-service-install "$target_dir" "$target" "$seed" "$target_mode" "$SERVICE_USER" "$target_owner"
else
[ "$(id -un)" = "$SERVICE_USER" ] || error "Cannot converge Homeboy as $(id -un): expected '$SERVICE_USER'."
HOME="$SERVICE_HOME" PATH="$service_path" /bin/bash -c "$service_script" homeboy-service-install "$target_dir" "$target" "$seed" "$target_mode" "$SERVICE_USER" "$target_owner"
fi
}

homeboy_provision_service_bin() {
homeboy_uses_service_owned_bin || return 0

local target source target_dir local_dir group
local target source target_dir managed_prefix owner group target_mode target_owner prefix_mode
target="$(homeboy_service_bin)"
target_dir="$(dirname "$target")"
local_dir="$(dirname "$target_dir")"
source="$(homeboy_system_bin)"
managed_prefix="$(dirname "$target_dir")"
homeboy_managed_prefix_safe "$target" || error "Refusing unsafe managed Homeboy path '$target'; expected a non-symlinked bin/homeboy under a root-owned wp-coding-agents prefix outside SERVICE_HOME."

if [ -e "$managed_prefix" ]; then
prefix_mode="$(file_mode "$managed_prefix" 2>/dev/null || true)"
[ "$prefix_mode" = 755 ] || error "Refusing existing managed Homeboy prefix '$managed_prefix' with mode ${prefix_mode:-unknown}; inspect its contents and have an administrator set this dedicated prefix to root:root 0755 before retrying."
fi

source=""
local legacy="$SERVICE_HOME/.local/bin/homeboy"
if [ -x "$legacy" ] && [ "$legacy" != "$target" ]; then source="$legacy"; else source="$(homeboy_system_bin)"; fi
[ -n "$source" ] || [ -x "$target" ] || return 0
if [ "${DRY_RUN:-false}" = true ]; then
[ -x "$target" ] || [ -n "$source" ] || return 0
[ -x "$target" ] || echo -e "${BLUE}[dry-run]${NC} install -D -m 0755 '$source' '$target' as service-owned Homeboy"
echo -e "${BLUE}[dry-run]${NC} chown '$SERVICE_USER' '$local_dir' '$target_dir' '$target'"
[ -x "$target" ] || echo -e "${BLUE}[dry-run]${NC} seed managed Homeboy from '$source' into '$target' as '$SERVICE_USER'"
echo -e "${BLUE}[dry-run]${NC} validate/create root-owned prefix '$managed_prefix'; create or hand off '$target_dir' to '$SERVICE_USER'"
return 0
fi

if [ "$(id -u)" -ne 0 ] && [ "$(id -un)" != "$SERVICE_USER" ]; then
error "Cannot provision service-owned Homeboy as $(id -un): expected '$SERVICE_USER' or root."
fi

if [ ! -x "$target" ]; then
[ -n "$source" ] || return 0
[ "$source" != "$target" ] || return 0
run_cmd mkdir -p "$target_dir"
run_cmd install -m 0755 "$source" "$target"
fi
if [ "$(id -u)" -eq 0 ] && id -u "$SERVICE_USER" >/dev/null 2>&1; then
if [ "$(id -u)" -eq 0 ]; then
if [ ! -e "$managed_prefix" ]; then
run_cmd mkdir -m 0700 "$managed_prefix" || error "Could not exclusively create fresh managed Homeboy prefix '$managed_prefix'; inspect the path and retry."
owner="$(file_owner "$managed_prefix" 2>/dev/null || true)"
[ "$owner" = root ] || error "New managed Homeboy prefix is not root-owned: $managed_prefix"
run_cmd chmod 0700 "$managed_prefix"
[ "$(file_mode "$managed_prefix" 2>/dev/null || true)" = 700 ] || error "Fresh managed Homeboy prefix did not retain restrictive creation mode before setup: $managed_prefix"
run_cmd chmod 0755 "$managed_prefix"
fi

if [ ! -e "$target_dir" ]; then
run_cmd mkdir -m 0700 "$target_dir"
owner="root"
else
[ -d "$target_dir" ] && [ ! -L "$target_dir" ] || error "Managed Homeboy bin is not a real directory: $target_dir"
owner="$(file_owner "$target_dir" 2>/dev/null || true)"
fi
group="$(id -gn "$SERVICE_USER" 2>/dev/null || printf '%s' "$SERVICE_USER")"
# Converge only this managed path, never the wider SERVICE_HOME tree. Both
# parents need service ownership for subsequent atomic binary upgrades.
run_cmd chown "$SERVICE_USER:$group" "$local_dir" "$target_dir" "$target"
fi
case "$owner" in
"$SERVICE_USER") ;;
root) run_cmd chown "$SERVICE_USER:$group" "$target_dir" ;;
*) error "Managed Homeboy bin has unexpected owner '$owner': $target_dir" ;;
esac
else
[ -d "$managed_prefix" ] && [ -d "$target_dir" ] || error "Managed Homeboy prefix/bin must be provisioned by root before service-user setup."
[ "$(file_mode "$managed_prefix" 2>/dev/null || true)" = 755 ] || error "Managed Homeboy prefix is not mode 755: $managed_prefix"
owner="$(file_owner "$target_dir" 2>/dev/null || true)"
[ "$owner" = "$SERVICE_USER" ] || error "Managed Homeboy bin is not service-owned: $target_dir"
fi

[ -n "$source" ] || source="$target"
target_mode="$(file_mode "$target" 2>/dev/null || true)"
target_owner="$(file_owner "$target" 2>/dev/null || true)"
homeboy_service_install_managed_binary "$target_dir" "$target" "$source" "$target_mode" "$target_owner" || error "Could not install or repair managed Homeboy as '$SERVICE_USER': $target"
[ "$(file_owner "$target" 2>/dev/null || true)" = "$SERVICE_USER" ] || error "Managed Homeboy is not owned by '$SERVICE_USER' after provisioning: $target"
homeboy_service_bin_ready || error "Managed Homeboy is not executable or its service-owned bin is not replaceable by '$SERVICE_USER': $target"
log "Provisioned service-owned Homeboy: $target"
}

Expand Down
2 changes: 1 addition & 1 deletion tests/__snapshots__/bridges/kimaki-systemd
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Type=simple
User=chubes
WorkingDirectory=/var/www/site
Environment=HOME=/home/chubes
Environment=PATH=/usr/bin:/usr/local/bin:/bin
Environment=PATH=/usr/local/lib/wp-coding-agents/bin:/usr/bin:/usr/local/bin:/bin
Environment=KIMAKI_DATA_DIR=/home/chubes/.kimaki
Environment=DATAMACHINE_SITE_PATH=/var/www/site
Environment=DATAMACHINE_WP_TRANSPORT_JSON="[\"wp\"]"
Expand Down
46 changes: 44 additions & 2 deletions tests/bridge-render.sh
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,14 @@ if echo "$REDACTED_DIFF" | grep -q 'secret-token'; then
fi

kimaki_env_block() {
local kimaki_bin_dir node_bin_dir path_value
local kimaki_bin_dir node_bin_dir homeboy_bin_dir path_value
kimaki_bin_dir=$(dirname "$KIMAKI_BIN")
node_bin_dir=$(_resolve_node_bin_dir "$KIMAKI_BIN")
path_value=$(_compose_path_value "$kimaki_bin_dir" "$node_bin_dir" /usr/local/bin /usr/bin /bin)
homeboy_bin_dir=""
if [ "$LOCAL_MODE" != true ] && [ "$SERVICE_USER" != root ]; then
homeboy_bin_dir=$(dirname "${WP_CODING_AGENTS_HOMEBOY_MANAGED_BIN:-/usr/local/lib/wp-coding-agents/bin/homeboy}")
fi
path_value=$(_compose_path_value "$homeboy_bin_dir" "$kimaki_bin_dir" "$node_bin_dir" /usr/local/bin /usr/bin /bin)
local transport_json
transport_json=$(wp_cli_transport_json)
transport_json=${transport_json//\\/\\\\}
Expand Down Expand Up @@ -155,6 +159,11 @@ echo "==> rendering snapshots"

# systemd ---------------------------------------------------------------
render_with_bridge kimaki render_systemd kimaki.service "$(kimaki_env_block)" > "$TMPDIR_NEW/kimaki-systemd"
if ! grep -Fq 'Environment=PATH=/usr/local/lib/wp-coding-agents/bin:/usr/bin:/usr/local/bin:/bin' "$TMPDIR_NEW/kimaki-systemd"; then
echo "FAIL: Kimaki systemd PATH does not include managed Homeboy directory"
exit 1
fi

render_with_bridge cc-connect render_systemd cc-connect.service "$(cc_connect_env_block)" > "$TMPDIR_NEW/cc-connect-systemd"
render_with_bridge telegram render_systemd opencode-serve.service "$(telegram_env_block)" > "$TMPDIR_NEW/telegram-serve-systemd"
render_with_bridge telegram render_systemd opencode-telegram.service "$(telegram_env_block)" > "$TMPDIR_NEW/telegram-bot-systemd"
Expand Down Expand Up @@ -233,6 +242,39 @@ fi
echo
echo "OK: all snapshots match"

# A real command lookup must pick managed Homeboy ahead of the legacy service
# home bin that also contains a `homeboy` executable.
mkdir -p "$TMPDIR_NEW/managed" "$TMPDIR_NEW/legacy"
PLATFORM="linux"
LOCAL_MODE=false
SERVICE_USER="chubes"
export PLATFORM LOCAL_MODE SERVICE_USER
printf '#!/bin/sh\nexit 0\n' > "$TMPDIR_NEW/managed/homeboy"
printf '#!/bin/sh\nexit 0\n' > "$TMPDIR_NEW/legacy/homeboy"
chmod 0755 "$TMPDIR_NEW/managed/homeboy" "$TMPDIR_NEW/legacy/homeboy"
export WP_CODING_AGENTS_HOMEBOY_MANAGED_BIN="$TMPDIR_NEW/managed/homeboy"
KIMAKI_BIN="$TMPDIR_NEW/legacy/kimaki"
KIMAKI_DATA_DIR="$SERVICE_HOME/.kimaki"
KIMAKI_CONFIG_DIR="/opt/kimaki-config"
KIMAKI_ENV="$(kimaki_env_block)"
KIMAKI_RENDERED="$(render_with_bridge kimaki render_systemd kimaki.service "$KIMAKI_ENV")"
RENDERED_PATH="$(printf '%s\n' "$KIMAKI_RENDERED" | sed -n 's/^Environment=PATH=//p' | sed -n '1p')"
RESOLVED_HOMEBOY="$(env PATH="$RENDERED_PATH" /bin/sh -c 'command -v homeboy')"
if [ "$RESOLVED_HOMEBOY" != "$TMPDIR_NEW/managed/homeboy" ]; then
echo "FAIL: Kimaki systemd PATH '$RENDERED_PATH' resolves $RESOLVED_HOMEBOY instead of managed Homeboy"
exit 1
fi
echo " ok Kimaki PATH resolves the managed binary ahead of the legacy home bin"
UPGRADE_ENV="$(_ensure_systemd_path_first "Environment=PATH=$TMPDIR_NEW/legacy:$TMPDIR_NEW/managed:/usr/bin:$TMPDIR_NEW/legacy" "$TMPDIR_NEW/managed")"
UPGRADE_PATH="$(printf '%s\n' "$UPGRADE_ENV" | sed -n 's/^Environment=PATH=//p' | sed -n '1p')"
RESOLVED_HOMEBOY="$(env PATH="$UPGRADE_PATH" /bin/sh -c 'command -v homeboy')"
if [ "$RESOLVED_HOMEBOY" != "$TMPDIR_NEW/managed/homeboy" ] || [ "${UPGRADE_PATH%%:*}" != "$TMPDIR_NEW/managed" ]; then
echo "FAIL: upgraded Kimaki PATH '$UPGRADE_PATH' resolves $RESOLVED_HOMEBOY instead of managed Homeboy"
exit 1
fi
echo " ok upgrade moves managed Homeboy ahead of legacy PATH entries"
unset WP_CODING_AGENTS_HOMEBOY_MANAGED_BIN

# ---------------------------------------------------------------------------
echo "==> effective-prompt runner resolution"
# ---------------------------------------------------------------------------
Expand Down
Loading
Loading