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
14 changes: 13 additions & 1 deletion lib/cmd_deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -546,13 +546,25 @@ cmd_health() {
compose_cmd=$(resolve_compose_cmd "$stack" "$env_file" "$services" "$bg_project")

local health_rc=0
health_run_all "$stack" "$compose_cmd" "$compose_file" "$json_flag" || health_rc=$?
local health_output=""
if [ -n "$json_flag" ]; then
# Defer JSON until after on_health_fail runs so hook chatter cannot trail
# the machine-readable result. Remote dispatch may append its known
# non-zero wrapper afterward; MCP handles that exact trailer separately.
health_output=$(health_run_all "$stack" "$compose_cmd" "$compose_file" "$json_flag") || health_rc=$?
else
health_run_all "$stack" "$compose_cmd" "$compose_file" "$json_flag" || health_rc=$?
fi

if [ "$health_rc" -ne 0 ]; then
# Fire on_health_fail hook (warn-only — never mask the original exit code)
HEALTH_STATUS="$health_rc" fire_hook_or_warn on_health_fail "$stack_dir"
fi

if [ -n "$json_flag" ]; then
printf '%s\n' "$health_output"
fi

return "$health_rc"
}

Expand Down
26 changes: 19 additions & 7 deletions lib/mcp/tools.sh
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,26 @@ _mcp_arg_lines() {
# repo's convention — see lib/utils.sh:60 — e.g. env_apply_gen_layer's
# "Could not decrypt generated env layer" on a stack using
# env/*.gen.enc.env without an age identity present) land ahead of a
# command's --json body rather than being separated onto stderr. A plain
# `jq -e .` over the whole capture then fails even though the call
# succeeded and produced valid JSON. This scans line-by-line for the first
# `{`/`[`-prefixed line whose suffix (to the end of the text) parses as
# complete JSON, and echoes that suffix. Echoes nothing and returns 1 if no
# such suffix exists.
# command's --json body rather than being separated onto stderr. Remote
# health adds one more known shape: run_remote_strut appends its generic
# failure diagnostic after valid degraded/unhealthy JSON because those
# domain states deliberately exit non-zero. Remove only that exact final
# wrapper line, then scan line-by-line for the first `{`/`[`-prefixed line
# whose remaining suffix parses as complete JSON. Echoes nothing and
# returns 1 if no such suffix exists.
_mcp_json_tail() {
local text="$1" line_no candidate
local text="$1" line_no candidate last_line
local remote_failure="Remote command failed — check VPS_HOST and SSH access"

if [[ "$text" == *$'\n'* ]]; then
last_line="${text##*$'\n'}"
case "$last_line" in
"✗ $remote_failure"|"✗ ["*"] $remote_failure")
text="${text%$'\n'*}"
;;
esac
fi

while IFS= read -r line_no; do
candidate=$(printf '%s\n' "$text" | tail -n "+$line_no")
if printf '%s' "$candidate" | jq -e . > /dev/null 2>&1; then
Expand Down
Loading