From 1b2bbcf27f5734eabe0f9e054a84f7c06075f9fe Mon Sep 17 00:00:00 2001 From: Yanyun Liao Date: Thu, 16 Jul 2026 18:09:02 +0800 Subject: [PATCH] fix(install): name the non-interactive trigger in the --station-deepseek conflict error (#7009) When --station-deepseek conflicts with non-interactive mode, the error printed an identical message no matter what caused it, so users could not tell why it was rejected. main() exports NON_INTERACTIVE into NEMOCLAW_NON_INTERACTIVE, so the origin cannot be recovered at error time. Record the trigger during flag parsing and name it in the message: ... cannot be combined with non-interactive mode (triggered by: the --non-interactive flag). ... cannot be combined with non-interactive mode (triggered by: NEMOCLAW_NON_INTERACTIVE=1). The "(triggered by: ...)" clause is only appended when the origin is known, so direct callers that set NON_INTERACTIVE without going through flag parsing still get a clean message. Companion to #7008 (which stops notice acceptance from implying non-interactive for --station-deepseek); kept as a separate PR per issue. This PR touches only the trigger tracking and the error message, not the decouple, so the two do not overlap. Signed-off-by: Yanyun Liao --- scripts/install.sh | 24 ++++++++++++++++++++++-- test/install-express-prompt.test.ts | 14 +++++++++++--- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 591cc4cad69..6bbc5090a55 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -2885,7 +2885,16 @@ validate_station_deepseek_override() { error "--station-deepseek cannot be combined with NEMOCLAW_NO_EXPRESS=1. Remove one override." fi if [ "${NON_INTERACTIVE:-}" = "1" ]; then - error "--station-deepseek selects the DGX Station express prompt and cannot be combined with --non-interactive." + # #7009: name what actually put the run in non-interactive mode so the user + # can act on it. NON_INTERACTIVE_SOURCE is recorded in main() where the + # origin is still known (main exports NON_INTERACTIVE into + # NEMOCLAW_NON_INTERACTIVE, erasing the distinction here). Omit the clause + # for direct callers that set NON_INTERACTIVE without going through main. + local trigger_note="" + if [ -n "${NON_INTERACTIVE_SOURCE:-}" ]; then + trigger_note=" (triggered by: ${NON_INTERACTIVE_SOURCE})" + fi + error "--station-deepseek selects the DGX Station express prompt and cannot be combined with non-interactive mode${trigger_note}." fi if [ -n "${NEMOCLAW_PROVIDER:-}" ]; then error "--station-deepseek conflicts with NEMOCLAW_PROVIDER=${NEMOCLAW_PROVIDER}. Remove the provider override to use Station express install." @@ -3099,12 +3108,20 @@ main() { # Parse flags NON_INTERACTIVE="" + # #7009: record what put the run in non-interactive mode so conflict errors + # (e.g. validate_station_deepseek_override) can name the trigger. main() + # exports NON_INTERACTIVE into NEMOCLAW_NON_INTERACTIVE below, so the origin + # cannot be recovered from the env at error time — track it here instead. + NON_INTERACTIVE_SOURCE="" ACCEPT_THIRD_PARTY_SOFTWARE="" FRESH="" STATION_DEEPSEEK="" for arg in "$@"; do case "$arg" in - --non-interactive) NON_INTERACTIVE=1 ;; + --non-interactive) + NON_INTERACTIVE=1 + NON_INTERACTIVE_SOURCE="the --non-interactive flag" + ;; --yes-i-accept-third-party-software) ACCEPT_THIRD_PARTY_SOFTWARE=1 ;; --fresh) FRESH=1 ;; --station-deepseek) STATION_DEEPSEEK=1 ;; @@ -3126,6 +3143,9 @@ main() { done # Also honor env var NON_INTERACTIVE="${NON_INTERACTIVE:-${NEMOCLAW_NON_INTERACTIVE:-}}" + if [ "${NON_INTERACTIVE:-}" = "1" ] && [ -z "${NON_INTERACTIVE_SOURCE:-}" ]; then + NON_INTERACTIVE_SOURCE="NEMOCLAW_NON_INTERACTIVE=1" + fi ACCEPT_THIRD_PARTY_SOFTWARE="${ACCEPT_THIRD_PARTY_SOFTWARE:-${NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE:-}}" FRESH="${FRESH:-${NEMOCLAW_FRESH:-}}" diff --git a/test/install-express-prompt.test.ts b/test/install-express-prompt.test.ts index 65ba31f39fd..54b3303baa3 100644 --- a/test/install-express-prompt.test.ts +++ b/test/install-express-prompt.test.ts @@ -296,12 +296,20 @@ detect_express_platform message: /--station-deepseek requires a detected DGX Station \(detected: DGX Spark\)/, }, { - name: "a conflicting non-interactive flag", + name: "a conflicting non-interactive flag (names the flag as the trigger)", args: ["--station-deepseek", "--non-interactive"], platform: "DGX Station", env: {}, message: - /--station-deepseek selects the DGX Station express prompt and cannot be combined with --non-interactive/, + /--station-deepseek selects the DGX Station express prompt and cannot be combined with non-interactive mode \(triggered by: the --non-interactive flag\)/, + }, + { + name: "a conflicting NEMOCLAW_NON_INTERACTIVE env var (names the env var as the trigger)", + args: ["--station-deepseek"], + platform: "DGX Station", + env: { NEMOCLAW_NON_INTERACTIVE: "1" }, + message: + /--station-deepseek selects the DGX Station express prompt and cannot be combined with non-interactive mode \(triggered by: NEMOCLAW_NON_INTERACTIVE=1\)/, }, { name: "a conflicting Station model", @@ -358,7 +366,7 @@ main "$@" it.each([ ["NEMOCLAW_NO_EXPRESS", "1", /cannot be combined with NEMOCLAW_NO_EXPRESS=1/], - ["NON_INTERACTIVE", "1", /cannot be combined with --non-interactive/], + ["NON_INTERACTIVE", "1", /cannot be combined with non-interactive mode/], ["NEMOCLAW_PROVIDER", "install-vllm", /conflicts with NEMOCLAW_PROVIDER=install-vllm/], ])("rejects %s when the Station demo override would otherwise be ignored", (name, value, message) => { const result = runExpressPromptWithTty("\n", "pipe", "DGX Station", {