Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
38 changes: 34 additions & 4 deletions cli/azd/cmd/auto_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"log"
"os"
"slices"
"strconv"
"strings"

"github.com/azure/azure-dev/cli/azd/internal"
Expand Down Expand Up @@ -617,6 +618,11 @@ func CreateGlobalFlagSet() *pflag.FlagSet {
"no-prompt",
false,
"Accepts the default value instead of prompting, or it fails if there is no default.")
globalFlags.Bool(
"non-interactive",
false,
"Alias for --no-prompt.")
_ = globalFlags.MarkHidden("non-interactive")

// The telemetry system is responsible for reading these flags value and using it to configure the telemetry
// system, but we still need to add it to our flag set so that when we parse the command line with Cobra we
Expand Down Expand Up @@ -670,11 +676,35 @@ func ParseGlobalFlags(args []string, opts *internal.GlobalCommandOptions) error
opts.NoPrompt = boolVal
}

// Agent Detection: If --no-prompt was not explicitly set and we detect an AI coding agent
// as the caller, automatically enable no-prompt mode for non-interactive execution.
// --non-interactive is an alias for --no-prompt
if boolVal, err := globalFlagSet.GetBool("non-interactive"); err == nil && boolVal {
opts.NoPrompt = true
}

// Check if either flag was explicitly provided on the command line
noPromptFlag := globalFlagSet.Lookup("no-prompt")
noPromptExplicitlySet := noPromptFlag != nil && noPromptFlag.Changed
if !noPromptExplicitlySet && agentdetect.IsRunningInAgent() {
nonInteractiveFlag := globalFlagSet.Lookup("non-interactive")
flagExplicitlySet := (noPromptFlag != nil && noPromptFlag.Changed) ||
(nonInteractiveFlag != nil && nonInteractiveFlag.Changed)

// Environment variable: AZD_NON_INTERACTIVE enables no-prompt mode when set to a
// truthy value (parsed via strconv.ParseBool: "true", "1", "TRUE", etc.).
// Explicit flags take precedence over this env var.
// When this env var is present (regardless of value), it also suppresses
// agent auto-detection since the user has made an explicit choice.
envVarPresent := false
if !flagExplicitlySet {
if envVal, ok := os.LookupEnv("AZD_NON_INTERACTIVE"); ok {
envVarPresent = true
if parsed, err := strconv.ParseBool(envVal); err == nil && parsed {
Comment thread
spboyer marked this conversation as resolved.
opts.NoPrompt = true
}
}
}

// Agent Detection: If no explicit flag or env var was set and we detect an AI coding
// agent as the caller, automatically enable no-prompt mode for non-interactive execution.
if !flagExplicitlySet && !envVarPresent && agentdetect.IsRunningInAgent() {
opts.NoPrompt = true
}

Expand Down
119 changes: 119 additions & 0 deletions cli/azd/cmd/auto_install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,3 +421,122 @@ func TestParseGlobalFlags_AgentDetection(t *testing.T) {
})
}
}

func TestParseGlobalFlags_NonInteractiveAliasAndEnvVar(t *testing.T) {
tests := []struct {
name string
args []string
envKey string
envVal string
wantNoPrompt bool
}{
{
name: "no flags or env",
args: []string{},
wantNoPrompt: false,
},
{
name: "--no-prompt sets NoPrompt",
args: []string{"--no-prompt"},
wantNoPrompt: true,
},
{
name: "--non-interactive sets NoPrompt",
args: []string{"--non-interactive"},
wantNoPrompt: true,
},
{
name: "--no-prompt=false keeps NoPrompt false",
args: []string{"--no-prompt=false"},
wantNoPrompt: false,
},
{
name: "AZD_NON_INTERACTIVE=true sets NoPrompt",
args: []string{},
envKey: "AZD_NON_INTERACTIVE",
envVal: "true",
wantNoPrompt: true,
},
{
name: "AZD_NON_INTERACTIVE=1 sets NoPrompt",
args: []string{},
envKey: "AZD_NON_INTERACTIVE",
envVal: "1",
wantNoPrompt: true,
},
{
name: "AZD_NON_INTERACTIVE=false does not set NoPrompt",
args: []string{},
envKey: "AZD_NON_INTERACTIVE",
envVal: "false",
wantNoPrompt: false,
},
{
name: "AZD_NON_INTERACTIVE=0 does not set NoPrompt",
args: []string{},
envKey: "AZD_NON_INTERACTIVE",
envVal: "0",
wantNoPrompt: false,
},
{
name: "explicit --no-prompt=false overrides env true",
args: []string{"--no-prompt=false"},
envKey: "AZD_NON_INTERACTIVE",
envVal: "true",
wantNoPrompt: false,
},
{
name: "explicit --no-prompt overrides env false",
args: []string{"--no-prompt"},
envKey: "AZD_NON_INTERACTIVE",
envVal: "false",
wantNoPrompt: true,
},
{
name: "--non-interactive overrides env false",
args: []string{"--non-interactive"},
envKey: "AZD_NON_INTERACTIVE",
envVal: "false",
wantNoPrompt: true,
},
{
name: "AZD_NON_INTERACTIVE=TRUE (uppercase)",
args: []string{},
envKey: "AZD_NON_INTERACTIVE",
envVal: "TRUE",
wantNoPrompt: true,
},
{
name: "both flags coexist",
args: []string{"--no-prompt", "--non-interactive"},
wantNoPrompt: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Clear agent detection to isolate from ambient environment
clearAgentEnvVarsForTest(t)
Comment thread
spboyer marked this conversation as resolved.
agentdetect.ResetDetection()

// Skip if we're inside an agent and expect false
if !tt.wantNoPrompt && tt.envKey == "" && len(tt.args) == 0 {
if agentdetect.GetCallingAgent().Detected {
t.Skip("skipping: agent process detected")
}
agentdetect.ResetDetection()
}

if tt.envKey != "" {
t.Setenv(tt.envKey, tt.envVal)
}

Comment thread
spboyer marked this conversation as resolved.
Outdated
opts := &internal.GlobalCommandOptions{}
err := ParseGlobalFlags(tt.args, opts)
require.NoError(t, err)
assert.Equal(t, tt.wantNoPrompt, opts.NoPrompt)

agentdetect.ResetDetection()
})
}
}
11 changes: 9 additions & 2 deletions cli/azd/internal/global_command_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,15 @@ type GlobalCommandOptions struct {
// launched tools. It's enabled with `--debug`, for any command.
EnableDebugLogging bool

// when true, interactive prompts should behave as if the user selected the default value.
// if there is no default value the prompt returns an error.
// NoPrompt controls non-interactive mode. When true, interactive prompts should behave as
// if the user selected the default value. If there is no default value the prompt returns
// an error.
//
// Can be enabled via:
// - --no-prompt flag
// - --non-interactive flag (alias for --no-prompt)
// - AZD_NON_INTERACTIVE=true environment variable
Comment thread
spboyer marked this conversation as resolved.
// - Automatic agent detection (lowest priority)
NoPrompt bool

// EnableTelemetry indicates if telemetry should be sent.
Expand Down
Loading