From 099b5c4353253b6893152dcf36d44603dbd223c2 Mon Sep 17 00:00:00 2001 From: "gfargo-horizon-agent[bot]" <294710345+gfargo-horizon-agent[bot]@users.noreply.github.com> Date: Sun, 2 Aug 2026 16:29:32 +0000 Subject: [PATCH 1/2] fix(status): remove asymmetries that block remote dispatch for status cmd_status differed from cmd_health in two ways that broke the MCP strut_status tool for stacks resolved purely through strut.conf [stacks]/[hosts] topology (no base per-env file on disk): - validate_env_file was called unconditionally, so a missing env file hard-failed status instead of falling through to should_dispatch_remote the way health does. - the remote dispatch branch didn't forward --json, so a remote status call (which the MCP tool always requests) silently dropped the flag. Both are now handled the same way cmd_health already does it. --- lib/cmd_deploy.sh | 12 +++++++++++- tests/test_cmd_status_remote.bats | 31 +++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/lib/cmd_deploy.sh b/lib/cmd_deploy.sh index 7b297d4..7ca8f83 100644 --- a/lib/cmd_deploy.sh +++ b/lib/cmd_deploy.sh @@ -563,12 +563,22 @@ cmd_status() { local env_file="$CMD_ENV_FILE" local env_name="$CMD_ENV_NAME" local services="$CMD_SERVICES" - validate_env_file "$env_file" + local json_flag="$CMD_JSON" + # Match cmd_health: a topology-only stack (VPS_HOST/host layer supplied + # entirely via strut.conf [stacks]/[hosts], no base per-env file) is a + # valid config for a read-only command — don't hard-fail just because + # there's nothing to validate. + if [ -f "$env_file" ]; then + validate_env_file "$env_file" + fi # Prefer remote execution for stacks that map to a VPS host, so status # reflects the real remote containers instead of the (empty) local daemon. if should_dispatch_remote; then local remote_args="status" + if [ -n "$json_flag" ]; then + remote_args="$remote_args --json" + fi if [ -n "$services" ]; then remote_args="$remote_args --services $services" fi diff --git a/tests/test_cmd_status_remote.bats b/tests/test_cmd_status_remote.bats index 3defece..3f04a6d 100644 --- a/tests/test_cmd_status_remote.bats +++ b/tests/test_cmd_status_remote.bats @@ -191,10 +191,37 @@ EOF [[ "$output" == *"full"* ]] } -@test "cmd_status: fails gracefully when env file missing" { +@test "cmd_status: --json flag forwarded to remote" { + export VPS_HOST="vps.example.com" + export CMD_JSON="--json" + + run cmd_status + [ "$status" -eq 0 ] + [[ "$output" == *"--json"* ]] +} + +# strut#501: a topology-only stack (VPS_HOST supplied entirely via +# strut.conf [stacks]/[hosts], no base per-env file on disk) is a valid, +# read-only-command-friendly config — matches cmd_health's tolerance below. +# Previously cmd_status hard-failed here instead of dispatching remote. +@test "cmd_status: dispatches via SSH when env file is missing but VPS_HOST is set" { + export CMD_ENV_FILE="$TEST_TMP/nonexistent.env" + export VPS_HOST="vps.example.com" + + run cmd_status + [ "$status" -eq 0 ] + [[ "$output" == *"ssh"* ]] + [[ "$output" == *"status"* ]] +} + +@test "cmd_status: runs locally when env file is missing and VPS_HOST is empty" { export CMD_ENV_FILE="$TEST_TMP/nonexistent.env" + export VPS_HOST="" + run cmd_status - [[ "$output" == *"not found"* ]] || [ "$status" -ne 0 ] + [ "$status" -eq 0 ] + [[ "$output" == *"COMPOSE"* ]] + [[ "$output" != *"ssh"* ]] } # ── cmd_health remote ───────────────────────────────────────────────────────── From 9fe2cbc9004a14ba6734021237aa7bfc602e540d Mon Sep 17 00:00:00 2001 From: "gfargo-horizon-agent[bot]" <294710345+gfargo-horizon-agent[bot]@users.noreply.github.com> Date: Sun, 2 Aug 2026 17:06:51 +0000 Subject: [PATCH 2/2] fix(status): only skip env validation before remote-dispatch check The previous fix for cmd_status skipped validate_env_file whenever the env file didn't exist, which silently let a local run through even when --env pointed at a typo'd/nonexistent name (broke tests/test_cli_env_parsing.bats). Move validate_env_file back so it's only skipped ahead of the should_dispatch_remote check (needed for a topology-only stack with no base per-env file), and still runs unconditionally once we know we're staying local. --- lib/cmd_deploy.sh | 16 +++++++++------- tests/test_cmd_status_remote.bats | 6 ++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/cmd_deploy.sh b/lib/cmd_deploy.sh index 7ca8f83..8ced835 100644 --- a/lib/cmd_deploy.sh +++ b/lib/cmd_deploy.sh @@ -564,16 +564,14 @@ cmd_status() { local env_name="$CMD_ENV_NAME" local services="$CMD_SERVICES" local json_flag="$CMD_JSON" - # Match cmd_health: a topology-only stack (VPS_HOST/host layer supplied - # entirely via strut.conf [stacks]/[hosts], no base per-env file) is a - # valid config for a read-only command — don't hard-fail just because - # there's nothing to validate. - if [ -f "$env_file" ]; then - validate_env_file "$env_file" - fi # Prefer remote execution for stacks that map to a VPS host, so status # reflects the real remote containers instead of the (empty) local daemon. + # Check this before validating the env file: a topology-only stack + # (VPS_HOST/host layer supplied entirely via strut.conf [stacks]/[hosts], + # no base per-env file on disk) is a valid config for this read-only + # command and shouldn't hard-fail before we even know we're dispatching + # remote. if should_dispatch_remote; then local remote_args="status" if [ -n "$json_flag" ]; then @@ -586,6 +584,10 @@ cmd_status() { return $? fi + # Local path: we're not dispatching remote, so an explicitly-requested but + # missing env file (e.g. a typo'd --env) must still fail loudly here. + validate_env_file "$env_file" + # Local path: query the local Docker daemon and show where we're looking. # Blue-green stacks run under a -- project — target # the active color, not the plain - project (strut#384). diff --git a/tests/test_cmd_status_remote.bats b/tests/test_cmd_status_remote.bats index 3f04a6d..c3a2505 100644 --- a/tests/test_cmd_status_remote.bats +++ b/tests/test_cmd_status_remote.bats @@ -214,14 +214,12 @@ EOF [[ "$output" == *"status"* ]] } -@test "cmd_status: runs locally when env file is missing and VPS_HOST is empty" { +@test "cmd_status: fails gracefully when env file is missing and VPS_HOST is empty" { export CMD_ENV_FILE="$TEST_TMP/nonexistent.env" export VPS_HOST="" run cmd_status - [ "$status" -eq 0 ] - [[ "$output" == *"COMPOSE"* ]] - [[ "$output" != *"ssh"* ]] + [[ "$output" == *"not found"* ]] || [ "$status" -ne 0 ] } # ── cmd_health remote ─────────────────────────────────────────────────────────