Skip to content

feat(provision): prompt to cancel Azure deployment on Ctrl+C (Bicep)#7795

Open
vhvb1989 wants to merge 10 commits intomainfrom
vivazqu/provision-cancel-ctrlc
Open

feat(provision): prompt to cancel Azure deployment on Ctrl+C (Bicep)#7795
vhvb1989 wants to merge 10 commits intomainfrom
vivazqu/provision-cancel-ctrlc

Conversation

@vhvb1989
Copy link
Copy Markdown
Member

Closes #2810

Summary

When a user presses Ctrl+C during `azd provision` or `azd up` while a Bicep deployment is in flight on Azure, azd now pauses and asks whether to:

  1. Leave the Azure deployment running and stop azd (default — safest), or
  2. Cancel the Azure deployment via the ARM Cancel API and wait for a terminal state.

Previously, Ctrl+C exited azd immediately while the deployment kept running on Azure with no easy follow-up.

Behavior

  • Re-entrant Ctrl+C presses while the prompt is up are ignored, so the user can choose deliberately.
  • When "Cancel" is selected, azd submits the ARM cancel request (30s timeout) and polls for a terminal state (up to 2 min). Outcomes are reported clearly: canceled, too-late, timed-out, or failed.
  • The portal URL is always printed so the user can follow up manually.
  • In non-interactive mode (no TTY) the prompt cannot be shown, so azd defaults to "leave running" — never silently cancels.

Provider scope

Provider Behavior
Bicep (sub + RG) Interactive prompt (new)
Deployment Stacks Treated as "leave running" — Stacks ARM API has no per-deployment cancel surface
Terraform Unchanged

Implementation

  • `pkg/input/interrupt.go` — stack-based `PushInterruptHandler` API; `watchTerminalInterrupt` consults registered handlers and suppresses re-entrant signals.
  • `pkg/azapi` + `pkg/infra/scope.go` — `Cancel` methods on `DeploymentService` / `Deployment` for both scopes.
  • `pkg/infra/provisioning/cancel.go` — 4 typed sentinel errors.
  • `pkg/infra/provisioning/bicep/interrupt.go` — prompt + cancel/poll flow.
  • `bicep_provider.go::Deploy` — wires the handler around the in-flight ARM call; coordinates with the existing progress reporter so the prompt renders cleanly.
  • `internal/cmd/errors.go` — telemetry mapping (`provision.cancellation` attribute + sentinel codes).
  • `cmd/middleware/error.go` — sentinels bypass agent troubleshooting.

Tests

  • `pkg/infra/scope_test.go::TestScopeCancel` — Cancel on both scopes + error propagation.
  • `pkg/input/interrupt_test.go` — LIFO stack + re-entrant suppression.
  • `pkg/infra/provisioning/bicep/interrupt_test.go` — `isTerminalProvisioningState` + `applyInterruptOutcome` table tests.

Validation

  • `gofmt`, `go fix`, `golangci-lint run ./...`, `cspell` — clean.
  • All impacted package tests pass: `pkg/infra/...`, `pkg/input/...`, `pkg/azapi/...`, `cmd/middleware/...`, `internal/cmd/...`.

Docs

  • `cli/azd/docs/provision-cancellation.md` — user-facing behavior, outcomes, provider scope, telemetry, non-interactive fallback.

When a user presses Ctrl+C during 'azd provision' or 'azd up' while a
Bicep deployment is in flight on Azure, azd now pauses and asks whether
to leave the Azure deployment running (default) or to cancel it via the
ARM Cancel API and wait for a terminal state.

- pkg/input: register-able interrupt handler stack with re-entrant
  Ctrl+C suppression while a handler is running.
- pkg/azapi + pkg/infra: Cancel methods on DeploymentService /
  Deployment for both subscription- and resource-group-scoped
  deployments. Deployment Stacks return 'not supported' (no Cancel API
  surface today).
- pkg/infra/provisioning: typed sentinel errors for the 4 outcomes
  (leave running / canceled / cancel timed out / cancel too late) plus
  telemetry attribute provision.cancellation.
- pkg/infra/provisioning/bicep: interactive prompt + cancel-and-poll
  flow with 30s cancel-request timeout and 2-min terminal-state wait.
- cmd/middleware + internal/cmd: bypass agent troubleshooting and map
  sentinels to telemetry codes.
- docs/provision-cancellation.md: user-facing behavior, outcomes,
  provider scope, telemetry, and non-interactive fallback.

Terraform and Deployment Stacks are out of scope and unchanged.

Closes #2810

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an interactive Ctrl+C flow for in-flight Bicep (ARM) deployments so users can explicitly choose to leave the deployment running or request Azure-side cancellation, with typed outcomes and telemetry.

Changes:

  • Introduces a stack-based SIGINT handler mechanism with re-entrant suppression for interactive prompts.
  • Adds ARM deployment cancel support for subscription- and resource-group-scoped deployments (with stack deployments explicitly unsupported).
  • Wires Bicep provision/deploy to prompt on Ctrl+C, emit typed sentinel outcomes, and record provision.cancellation telemetry; documents the behavior.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
cli/azd/pkg/input/interrupt.go New interrupt handler stack + re-entrant suppression primitives.
cli/azd/pkg/input/interrupt_test.go Unit tests for handler stack and re-entrancy behavior.
cli/azd/pkg/input/console.go Updates SIGINT watcher to consult registered handlers and ignore re-entrant interrupts.
cli/azd/pkg/infra/scope.go Adds Cancel(ctx) to infra.Deployment and implements for RG/sub scopes.
cli/azd/pkg/infra/scope_test.go Tests cancel endpoint calls for both scopes and error propagation.
cli/azd/pkg/infra/provisioning/cancel.go Adds provisioning cancellation sentinel errors surfaced to middleware.
cli/azd/pkg/infra/provisioning/bicep/interrupt.go Implements the interactive prompt + cancel/poll flow and outcome mapping.
cli/azd/pkg/infra/provisioning/bicep/interrupt_test.go Tests terminal-state detection and interrupt outcome application.
cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go Wires interrupt handler around the in-flight ARM deploy call and sets telemetry.
cli/azd/pkg/azapi/deployments.go Extends DeploymentService with cancel methods.
cli/azd/pkg/azapi/standard_deployments.go Implements ARM cancel for subscription + resource group deployments.
cli/azd/pkg/azapi/stack_deployments.go Implements cancel methods as unsupported for deployment stacks.
cli/azd/internal/tracing/fields/fields.go Adds provision.cancellation tracing attribute key.
cli/azd/internal/cmd/errors.go Maps new provisioning sentinel errors to classifications.
cli/azd/cmd/middleware/error.go Ensures new sentinels bypass agent troubleshooting.
cli/azd/docs/provision-cancellation.md Documents the Ctrl+C cancellation UX and outcomes.

Comment thread cli/azd/pkg/input/interrupt_test.go Outdated
Comment thread cli/azd/pkg/infra/provisioning/bicep/interrupt.go Outdated
Comment thread cli/azd/docs/provision-cancellation.md
Comment thread cli/azd/pkg/infra/scope_test.go
- pkg/input: LIFO test now invokes handlers and asserts distinct call
  counts to prove ordering.
- pkg/infra/provisioning: add ErrDeploymentCancelFailed sentinel so the
  cancel-request-failure path no longer misclassifies as a timeout;
  wire it through error middleware skip-list and telemetry mapping.
- pkg/infra: switch new TestScopeCancel subtests to t.Context().

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 6 comments.

Comment thread cli/azd/pkg/infra/provisioning/bicep/interrupt.go Outdated
Comment thread cli/azd/pkg/infra/provisioning/bicep/interrupt.go
Comment thread cli/azd/pkg/azapi/stack_deployments.go
Comment thread cli/azd/pkg/infra/provisioning/bicep/interrupt.go
Comment thread cli/azd/pkg/infra/provisioning/cancel.go Outdated
Comment thread cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go Outdated
- pkg/azapi: add typed ErrCancelNotSupported sentinel; stack
  CancelSubscriptionDeployment / CancelResourceGroupDeployment now
  return it instead of an opaque string.
- pkg/infra/provisioning/bicep: interrupt handler treats
  ErrCancelNotSupported as the safer 'leave running' outcome (matches
  documented stacks behavior + telemetry). Cancel-request error path
  routes through terminalToOutcome when the deployment is already in a
  terminal state, so the portal URL and consistent messaging are
  surfaced. Canceled terminal branch now prints the portal URL too.
- pkg/infra/provisioning: ErrDeploymentCancelFailed doc comment now
  references errors.Is/errors.As (matches the multi-%w joined-error
  wrapping pattern used here).
- pkg/infra/provisioning/bicep/bicep_provider: tear down the interrupt
  handler immediately after deployModule returns (sync.OnceFunc) to
  avoid a small window where a late Ctrl+C could surface the prompt
  over post-processing output.
- internal/cmd/errors: map ErrCancelNotSupported in classifySentinel.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Comment thread cli/azd/pkg/infra/provisioning/bicep/interrupt.go Outdated
Comment thread cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go Outdated
If Ctrl+C arrives but the ARM deployment happens to finish naturally
before the user picks an option in the prompt, the previous design
could take the success path and silently drop the interrupt.

- installDeploymentInterruptHandler now exposes a 'started' channel
  that is closed the instant Ctrl+C is received, before the prompt is
  shown. deployCtx is also cancelled immediately so PollUntilDone
  unblocks ASAP.
- BicepProvider.Deploy block-receives the outcome whenever 'started'
  is closed (instead of a non-blocking drain), so the user's choice is
  always honored regardless of who wins the race.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.

Comment thread cli/azd/pkg/infra/provisioning/cancel.go Outdated
Comment thread cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go Outdated
Comment thread cli/azd/pkg/input/console.go
- pkg/input/console: watchTerminalInterrupt now reserves the running
  slot before consulting the handler stack so re-entrant Ctrl+C is
  suppressed even if the stack is briefly empty (e.g. handler popped
  but still executing the prompt).
- pkg/infra/provisioning/bicep/bicep_provider: defer cleanup until
  after the interrupt outcome is received so a second Ctrl+C during
  the prompt is still suppressed; the no-interrupt path tears down
  immediately as before.
- pkg/infra/provisioning/cancel: doc reads 'sentinel errors' instead
  of 'typed errors' to match the implementation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.

Comment thread cli/azd/pkg/input/interrupt.go Outdated
Comment thread cli/azd/pkg/infra/provisioning/bicep/interrupt.go Outdated
Comment thread cli/azd/pkg/infra/provisioning/bicep/interrupt.go
- pkg/input/interrupt: enforce strict LIFO when popping handlers
  (only pop when this handler is still top-of-stack), so out-of-order
  pops never accidentally remove unrelated newer handlers.
- pkg/infra/provisioning/bicep/interrupt: defensive default in
  terminalToOutcome now stops the spinner and emits a warning with
  the observed state and portal URL, leaving the UI clean if an
  unexpected terminal state is ever observed.
- pkg/infra/provisioning/bicep/interrupt: treat
  DeploymentProvisioningStateDeleted as terminal in the cancel poll
  so we don't keep polling until the deadline if the deployment is
  deleted out from under us. Test updated accordingly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.

Comment thread cli/azd/pkg/infra/provisioning/bicep/interrupt.go Outdated
Comment thread cli/azd/pkg/infra/provisioning/bicep/interrupt.go
Comment thread cli/azd/docs/provision-cancellation.md Outdated
- pkg/infra/provisioning/bicep/interrupt: wrap the interrupt handler
  closure with sync.OnceValue so close(started), cancelDeploy() and
  the outcome channel send all run at most once. Combined with the
  in-flight guard from tryStartInterruptHandler and the strict LIFO
  pop, additional Ctrl+C signals after the prompt completes can no
  longer panic or block on the buffered channel.
- pkg/infra/provisioning/bicep/interrupt: print the portal URL on
  the prompt-failure leave-running path so the user always has a
  link to follow up when the URL is available.
- docs/provision-cancellation: clarify that the portal URL is
  printed when available (not 'in every case').

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.

Comment thread cli/azd/pkg/input/interrupt.go
- pkg/input/interrupt: nil out the popped slot before truncating
  the interrupt stack so the GC can reclaim the popped handler and
  any state it captured, even before the underlying array is
  reallocated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- pkg/input/console: run the registered interrupt handler inline on
  the signal goroutine instead of in a nested goroutine. This
  removes the scheduling window where SIGINT was received but the
  handler had not yet run, which could let a deploy goroutine
  complete naturally and silently drop the Ctrl+C. Re-entrant signals
  remain suppressed via tryStartInterruptHandler.
- pkg/infra/provisioning/bicep/interrupt: switch the cancel poll
  loop to a time.Ticker and move the wait before each Get, so a
  slow Get cannot produce back-to-back ARM polls (preventing
  throttling).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.

// If the deployment is already in a terminal state, route through
// the same terminal-outcome reporter so the user sees consistent
// messaging (including the portal URL).
if state, getErr := deployment.Get(context.WithoutCancel(ctx)); getErr == nil &&
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the cancel-request error path, the follow-up deployment.Get(context.WithoutCancel(ctx)) call is unbounded (no timeout). If ARM is slow/unreachable, the interrupt handler can hang indefinitely after the user selected “Cancel”, preventing azd from exiting or surfacing the portal URL/outcome. Consider wrapping this Get in a short timeout context (similar to cancelRequestTimeout) so the interrupt flow always makes progress even when ARM is degraded.

Suggested change
if state, getErr := deployment.Get(context.WithoutCancel(ctx)); getErr == nil &&
getCtx, getDone := context.WithTimeout(
context.WithoutCancel(ctx), cancelRequestTimeout)
defer getDone()
if state, getErr := deployment.Get(getCtx); getErr == nil &&

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.

firstCalls++
return true
}
pop1 := PushInterruptHandler(first)
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test pushes a global interrupt handler but doesn’t register cleanup until the end of the test. If any require.* before pop1() fails, the handler can remain on the global stack and pollute later pkg/input tests. Consider calling t.Cleanup(pop1) (or defer pop1()) immediately after this PushInterruptHandler call.

Copilot uses AI. Check for mistakes.
secondCalls++
return true
}
pop2 := PushInterruptHandler(second)
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: if an assertion fails before pop2() runs, the global interrupt stack can be left in a dirty state. Register pop2 with t.Cleanup(pop2) (or defer pop2()) immediately after pushing it.

Copilot uses AI. Check for mistakes.
Comment on lines +340 to +344
switch state {
case azapi.DeploymentProvisioningStateCanceled,
azapi.DeploymentProvisioningStateFailed,
azapi.DeploymentProvisioningStateSucceeded,
azapi.DeploymentProvisioningStateDeleted:
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isTerminalProvisioningState includes DeploymentProvisioningStateDeleted as terminal, but terminalToOutcome does not handle Deleted explicitly and will treat it as an “unexpected terminal state” (and map it to cancel_too_late). Either handle Deleted explicitly in terminalToOutcome (with appropriate messaging/telemetry) or remove it from the terminal set if it shouldn’t be surfaced here.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@wbreza wbreza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewing as wbreza. Blocking on H1–H4 — genuine correctness/industry-standard Ctrl+C defects not covered by the existing 52 comments. Rest are architectural consistency + test coverage gaps to consider.

🔴 High — must-fix before merge (4)

H1 — Race between default branch and signal-goroutine handler dispatch

pkg/infra/provisioning/bicep/bicep_provider.go:790–802 + pkg/input/console.go:1000–1018

In watchTerminalInterrupt there are three sequential steps after SIGINT: ryStartInterruptHandler() → currentInterruptHandler() → handler() (where close(started) lives). Between step B and C the Go scheduler can run the main goroutine, which sees <-interruptStarted not yet closed, takes the default branch, and calls cleanupOnce() to pop + cancelDeploy(). Two failure modes:

  1. The captured handler runs the prompt over success output; the interruptCh send has no reader.
  2. If the pop happened before B, currentInterruptHandler() returns
    il and the goroutine hits os.Exit(1) after success.

Fix options: expose TryClaimInterrupt/ReleaseInterrupt so the default branch acquires the running slot before popping; or add an OnDispatch pre-handler callback invoked by the signal goroutine before handler() so interruptStarted is closed before handler() begins.

H2 — Handler panic leaves process unkillable from Ctrl+C

pkg/input/console.go:1013

go handled := handler() finishInterruptHandler() // never reached on panic
No
ecover(), not deferred. If a handler panics, interruptRunning=true forever AND the signal goroutine dies, so or range signalChan stops draining — every future Ctrl+C is silently dropped AND Go's default SIGINT handler is not restored. User must SIGKILL.

Fix:
go defer func() { recover(); signal.Stop(signalChan) }() for range signalChan { if !tryStartInterruptHandler() { continue } handler := currentInterruptHandler() func() { defer finishInterruptHandler() defer func() { if r := recover(); r != nil { log.Printf("interrupt handler panic: %v", r) } }() handled = handler() }() if !handled { os.Exit(1) } }

H3 — Second Ctrl+C is silently dropped; no industry-standard force-exit escape

pkg/infra/provisioning/bicep/interrupt.go (prompt at ~line 75, poll at 185–281)

Two windows where a second Ctrl+C has no effect:

  1. While the console.Select prompt is showing — ryStartInterruptHandler() gate + sync.OnceValue cached return both swallow the press.
  2. During the 2-minute cancel-await poll — interruptRunning stays true, all further SIGINTs are dropped silently with zero user feedback.

This violates the near-universal POSIX CLI convention — kubectl, erraform apply, docker compose down, git, docker run,
pm, cargo, and all azd peers treat a 2nd Ctrl+C as force-exit. Users who hit Ctrl+C by accident, or who see the cancel wait hanging, have no escape short of SIGKILL.

Fix: Track a press counter separately from interruptRunning. On 2nd press while a handler is running → os.Exit(130) (standard 128+SIGINT). Or nest a PushInterruptHandler inside the poll that aborts the wait and returns ErrDeploymentInterruptedLeaveRunning on re-press.

H4 — Process-global interrupt stack breaks IoC, parallel tests, multi-console hosts

pkg/input/interrupt.go:22–25

interruptStack, interruptMu, interruptRunning are package vars. pkg/input is shared UX infrastructure consumed via IoC — every other piece of Console state is scoped to the instance. Impacts:

  • .Parallel() tests using input collide silently. The current interrupt_test.go has to avoid parallelism with no documentation.
  • Embeddings that host multiple consoles (MCP server, dev-center-mode, potential agent harness hosting multiple azd sessions) share one handler stack — handlers leak across sessions.
  • Tests can't override the broker for unit isolation.

Fix: Define InterruptBroker interface with Push(InterruptHandler) (pop func()), register as a singleton in cmd/container.go, and inject into AskerConsole + BicepProvider. Keep the package-global only as a default-adapter fallback.


🟡 Medium (13) — architectural consistency + test coverage

Architecture / API design

  • M1 — DeploymentService.CancelSubscriptionDeployment + CancelResourceGroupDeployment duplicate the N² scope surface. Repo guidance says "extract shared helpers for multiple scopes". Either (a) CancelDeployment(ctx, scope) taking infra.Deployment, or (b) drop from the interface — each scope impl has a single caller (SubscriptionDeployment.Cancel / ResourceGroupDeployment.Cancel) that can call �rmDeployments.CancelAtSubscriptionScope directly.
  • M2 — ErrCancelNotSupported lives in pkg/azapi but is consumed via infra.Deployment in �icep/interrupt.go. Terraform/Pulumi will need the same sentinel and will either have to import �zapi or redefine. Move to pkg/infra/provisioning/cancel.go.
  • M3 — 5 new sentinels duplicated across cmd/middleware/error.go:90–96 (shouldSkipAgentHandling) AND internal/cmd/errors.go:287–299 (classifySentinel). #7797 is solving the same pattern with AuthInteractionError. Introduce UserDecisionError interface with UserDecisionCode() string. One �rrors.AsType check collapses both lists and absorbs ErrAbortedByUser, ErrSamplingDenied, ErrPreviewNotSupported as a follow-up cleanup.
  • M4 — BicepProvider.installDeploymentInterruptHandler calls console.Select, manages spinners, formats portal URLs. Provider owns interactive UX copy. Introduce a small InterruptDecider interface in pkg/infra/provisioning (returns a InterruptChoice enum). Default impl lives in pkg/input / pkg/ux. Terraform won't copy-paste this when it adds support.

Correctness / robustness

  • M5 — provision.cancellation attribute is only emitted by the Bicep path (3 call sites). Terraform and Stacks runs never emit it. count by (provision.cancellation) Kusto aggregations under-count. Either emit
    one in a shared wrapper or rename the key to provision.bicep.cancellation.
  • M6 — Non-TTY contract relies on console.Select returning an error. With --no-prompt or a pipe, Select may return a zero-value choice (not error) and the prompt can hang. Upfront check: if !console.IsUnformatted() || console.IsNoPrompt() { return leaveRunningOutcome } before calling Select.
  • M7 — Pre-PR Ctrl+C was instant in TTY provisions. Post-PR, Azure DevOps hosted agents, GitHub Actions ty: true, buildkite --tty users get a modal invisible prompt. Wrapping scripts will hang. Add AZD_PROVISION_INTERRUPT=abort|prompt escape hatch and document in docs/provision-cancellation.md.
  • M8 — watchTerminalInterrupt registers signal.Notify but never calls signal.Stop(signalChan). Goroutine + signal subscription leak per AskerConsole. Peer code already pairs them (pkg/ux/internal/input.go:83, �xtensions/azure.ai.agents/internal/cmd/run.go:179).
  • M9 — PushInterruptHandler's closure only truncates when len == idx+1. Out-of-order pop silently becomes a no-op — orphaned handler intercepts Ctrl+C forever. Panic in dev builds or trace-warn.

Test coverage (user's explicit ask on industry-standard Ctrl+C)

  • M10 — pkg/input/interrupt_test.go has zero race/signal-delivery/panic/re-entry tests. This is the primary test surface for the new subsystem. Missing: concurrent Push/Pop under -race, handler that recursively pushes, handler that panics, multiple SIGINTs delivered while a handler runs.
  • M11 — pkg/infra/provisioning/bicep/interrupt_test.go only covers the 2 pure helpers (TestIsTerminalProvisioningState, TestApplyInterruptOutcome). Missing: prompt accept → Cancel happy path, prompt accept → poll-budget-exhaust → ErrDeploymentCancelTimeout, cancel-submit timeout (30s), deployment already terminal on first Get → ErrDeploymentCancelTooLate, prompt reject → ErrDeploymentInterruptedLeaveRunning, non-TTY fallback, ErrCancelNotSupported → leave-running + elemetryValue=leave_running.
  • M12 — No test asserts provision.cancellation emits the 6 defined outcomes (
    one, leave_running, canceled, cancel_too_late, cancel_timed_out, cancel_failed). Silent telemetry regressions will sneak in.

Standards

  • M13 — erminalToOutcome(state, portalUrl, p, ctx) has ctx as last parameter and takes *BicepProvider as a parameter. Per AGENTS.md ctx must be first. Make it a method on BicepProvider like its neighbors (
    unInterruptPrompt, cancelAndAwaitTerminal, installDeploymentInterruptHandler).

🟢 Low (9)

  • L1 — Stacks cancel maps to internal.cancel_not_supported in classifySentinel but surfaces as ErrDeploymentInterruptedLeaveRunning (legitimate user outcome). Misclassifies legitimate UX as an internal error. Emit a secondary attribute like provision.cancellation.reason=stacks_unsupported.
  • L2 — �pplyInterruptOutcome uses mt.Errorf("%w: %w", outcome.err, deployErr). If the deploy failed with a non-cancel error racing the prompt, user sees "deployment was canceled by user request: template validation failed" even though cancel never happened. Use �rrors.Join or branch on �rrors.Is(deployErr, context.Canceled).
  • L3 — context.WithoutCancel decoupling in cancel/poll is untested. A future refactor could silently pass deployCtx and kill cancel requests.
  • L4 — scope_test.TestScopeCancel has no case asserting �rrors.Is(target.Cancel(ctx), azapi.ErrCancelNotSupported) for the stacks scope.
  • L5 — lastState hoisted outside the loop but only read within one inner block — inline it.
  • L6 — erminalToOutcome has no doc comment; peers do.
  • L7 — Mixed American/British spelling across one feature: "Cancelling", "Cancellation", vs ARM's Canceled / Canceling and existing sentinel ErrDeploymentCanceledByUser. Pick American to match ARM + codebase.
  • L8 — Stray blank line mid-|| chain in cmd/middleware/error.go:89.
  • L9 — Follow-up: no gRPC hook for extensions to register interrupt handlers. Not blocking this PR, but worth a tracking issue — today extensions running long ops can't opt into graceful cancel.

Regression audit

Verified clean:

  • pkg/ux/internal/input.go prompt SIGINT — independent signal.Notify channel, coexists correctly
  • �xtensions/azure.ai.agents — separate process group
  • Hooks, gRPC extensions, monitor, deploy, package — no PushInterruptHandler callers; pre-PR behavior preserved

Flying blind (no regression tests):

  • Hooks SIGINT forwarding on Unix — signal.Notify(os.Interrupt) now consumes the terminal-generated SIGINT before the kernel sends it to the process group. Child hook processes may no longer receive SIGINT when the user presses Ctrl+C during hook execution.
  • �zd up UX consistency — provision phase gets the new prompt; deploy phase still hard-exits. Inconsistent across one command.
  • SIGTERM — new handler stack covers SIGINT only. kill bypasses all this. Worth documenting explicitly.

Excellent feature overall. H1–H4 are the blockers. M-findings are a mix of architectural consistency (aligns with the direction #7797 is taking) and the test coverage that your "industry-standard" framing deserves. Happy to discuss any of these.

Copy link
Copy Markdown
Contributor

@wbreza wbreza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewing as wbreza. Blocking on H1–H4 — genuine correctness/industry-standard Ctrl+C defects not covered by the existing 52 comments. Rest are architectural consistency + test coverage gaps to consider.

🔴 High — must-fix before merge (4)

H1 — Race between default branch and signal-goroutine handler dispatch

pkg/infra/provisioning/bicep/bicep_provider.go:790–802 + pkg/input/console.go:1000–1018

In watchTerminalInterrupt there are three sequential steps after SIGINT: ryStartInterruptHandler() → currentInterruptHandler() → handler() (where close(started) lives). Between step B and C the Go scheduler can run the main goroutine, which sees <-interruptStarted not yet closed, takes the default branch, and calls cleanupOnce() to pop + cancelDeploy(). Two failure modes:

  1. The captured handler runs the prompt over success output; the interruptCh send has no reader.
  2. If the pop happened before B, currentInterruptHandler() returns
    il and the goroutine hits os.Exit(1) after success.

Fix options: expose TryClaimInterrupt/ReleaseInterrupt so the default branch acquires the running slot before popping; or add an OnDispatch pre-handler callback invoked by the signal goroutine before handler() so interruptStarted is closed before handler() begins.

H2 — Handler panic leaves process unkillable from Ctrl+C

pkg/input/console.go:1013

go handled := handler() finishInterruptHandler() // never reached on panic
No
ecover(), not deferred. If a handler panics, interruptRunning=true forever AND the signal goroutine dies, so or range signalChan stops draining — every future Ctrl+C is silently dropped AND Go's default SIGINT handler is not restored. User must SIGKILL.

Fix:
go defer func() { recover(); signal.Stop(signalChan) }() for range signalChan { if !tryStartInterruptHandler() { continue } handler := currentInterruptHandler() func() { defer finishInterruptHandler() defer func() { if r := recover(); r != nil { log.Printf("interrupt handler panic: %v", r) } }() handled = handler() }() if !handled { os.Exit(1) } }

H3 — Second Ctrl+C is silently dropped; no industry-standard force-exit escape

pkg/infra/provisioning/bicep/interrupt.go (prompt at ~line 75, poll at 185–281)

Two windows where a second Ctrl+C has no effect:

  1. While the console.Select prompt is showing — ryStartInterruptHandler() gate + sync.OnceValue cached return both swallow the press.
  2. During the 2-minute cancel-await poll — interruptRunning stays true, all further SIGINTs are dropped silently with zero user feedback.

This violates the near-universal POSIX CLI convention — kubectl, erraform apply, docker compose down, git, docker run,
pm, cargo, and all azd peers treat a 2nd Ctrl+C as force-exit. Users who hit Ctrl+C by accident, or who see the cancel wait hanging, have no escape short of SIGKILL.

Fix: Track a press counter separately from interruptRunning. On 2nd press while a handler is running → os.Exit(130) (standard 128+SIGINT). Or nest a PushInterruptHandler inside the poll that aborts the wait and returns ErrDeploymentInterruptedLeaveRunning on re-press.

H4 — Process-global interrupt stack breaks IoC, parallel tests, multi-console hosts

pkg/input/interrupt.go:22–25

interruptStack, interruptMu, interruptRunning are package vars. pkg/input is shared UX infrastructure consumed via IoC — every other piece of Console state is scoped to the instance. Impacts:

  • .Parallel() tests using input collide silently. The current interrupt_test.go has to avoid parallelism with no documentation.
  • Embeddings that host multiple consoles (MCP server, dev-center-mode, potential agent harness hosting multiple azd sessions) share one handler stack — handlers leak across sessions.
  • Tests can't override the broker for unit isolation.

Fix: Define InterruptBroker interface with Push(InterruptHandler) (pop func()), register as a singleton in cmd/container.go, and inject into AskerConsole + BicepProvider. Keep the package-global only as a default-adapter fallback.


🟡 Medium (13) — architectural consistency + test coverage

Architecture / API design

  • M1 — DeploymentService.CancelSubscriptionDeployment + CancelResourceGroupDeployment duplicate the N² scope surface. Repo guidance says "extract shared helpers for multiple scopes". Either (a) CancelDeployment(ctx, scope) taking infra.Deployment, or (b) drop from the interface — each scope impl has a single caller (SubscriptionDeployment.Cancel / ResourceGroupDeployment.Cancel) that can call �rmDeployments.CancelAtSubscriptionScope directly.
  • M2 — ErrCancelNotSupported lives in pkg/azapi but is consumed via infra.Deployment in �icep/interrupt.go. Terraform/Pulumi will need the same sentinel and will either have to import �zapi or redefine. Move to pkg/infra/provisioning/cancel.go.
  • M3 — 5 new sentinels duplicated across cmd/middleware/error.go:90–96 (shouldSkipAgentHandling) AND internal/cmd/errors.go:287–299 (classifySentinel). #7797 is solving the same pattern with AuthInteractionError. Introduce UserDecisionError interface with UserDecisionCode() string. One �rrors.AsType check collapses both lists and absorbs ErrAbortedByUser, ErrSamplingDenied, ErrPreviewNotSupported as a follow-up cleanup.
  • M4 — BicepProvider.installDeploymentInterruptHandler calls console.Select, manages spinners, formats portal URLs. Provider owns interactive UX copy. Introduce a small InterruptDecider interface in pkg/infra/provisioning (returns a InterruptChoice enum). Default impl lives in pkg/input / pkg/ux. Terraform won't copy-paste this when it adds support.

Correctness / robustness

  • M5 — provision.cancellation attribute is only emitted by the Bicep path (3 call sites). Terraform and Stacks runs never emit it. count by (provision.cancellation) Kusto aggregations under-count. Either emit
    one in a shared wrapper or rename the key to provision.bicep.cancellation.
  • M6 — Non-TTY contract relies on console.Select returning an error. With --no-prompt or a pipe, Select may return a zero-value choice (not error) and the prompt can hang. Upfront check: if !console.IsUnformatted() || console.IsNoPrompt() { return leaveRunningOutcome } before calling Select.
  • M7 — Pre-PR Ctrl+C was instant in TTY provisions. Post-PR, Azure DevOps hosted agents, GitHub Actions ty: true, buildkite --tty users get a modal invisible prompt. Wrapping scripts will hang. Add AZD_PROVISION_INTERRUPT=abort|prompt escape hatch and document in docs/provision-cancellation.md.
  • M8 — watchTerminalInterrupt registers signal.Notify but never calls signal.Stop(signalChan). Goroutine + signal subscription leak per AskerConsole. Peer code already pairs them (pkg/ux/internal/input.go:83, �xtensions/azure.ai.agents/internal/cmd/run.go:179).
  • M9 — PushInterruptHandler's closure only truncates when len == idx+1. Out-of-order pop silently becomes a no-op — orphaned handler intercepts Ctrl+C forever. Panic in dev builds or trace-warn.

Test coverage (user's explicit ask on industry-standard Ctrl+C)

  • M10 — pkg/input/interrupt_test.go has zero race/signal-delivery/panic/re-entry tests. This is the primary test surface for the new subsystem. Missing: concurrent Push/Pop under -race, handler that recursively pushes, handler that panics, multiple SIGINTs delivered while a handler runs.
  • M11 — pkg/infra/provisioning/bicep/interrupt_test.go only covers the 2 pure helpers (TestIsTerminalProvisioningState, TestApplyInterruptOutcome). Missing: prompt accept → Cancel happy path, prompt accept → poll-budget-exhaust → ErrDeploymentCancelTimeout, cancel-submit timeout (30s), deployment already terminal on first Get → ErrDeploymentCancelTooLate, prompt reject → ErrDeploymentInterruptedLeaveRunning, non-TTY fallback, ErrCancelNotSupported → leave-running + elemetryValue=leave_running.
  • M12 — No test asserts provision.cancellation emits the 6 defined outcomes (
    one, leave_running, canceled, cancel_too_late, cancel_timed_out, cancel_failed). Silent telemetry regressions will sneak in.

Standards

  • M13 — erminalToOutcome(state, portalUrl, p, ctx) has ctx as last parameter and takes *BicepProvider as a parameter. Per AGENTS.md ctx must be first. Make it a method on BicepProvider like its neighbors (
    unInterruptPrompt, cancelAndAwaitTerminal, installDeploymentInterruptHandler).

🟢 Low (9)

  • L1 — Stacks cancel maps to internal.cancel_not_supported in classifySentinel but surfaces as ErrDeploymentInterruptedLeaveRunning (legitimate user outcome). Misclassifies legitimate UX as an internal error. Emit a secondary attribute like provision.cancellation.reason=stacks_unsupported.
  • L2 — �pplyInterruptOutcome uses mt.Errorf("%w: %w", outcome.err, deployErr). If the deploy failed with a non-cancel error racing the prompt, user sees "deployment was canceled by user request: template validation failed" even though cancel never happened. Use �rrors.Join or branch on �rrors.Is(deployErr, context.Canceled).
  • L3 — context.WithoutCancel decoupling in cancel/poll is untested. A future refactor could silently pass deployCtx and kill cancel requests.
  • L4 — scope_test.TestScopeCancel has no case asserting �rrors.Is(target.Cancel(ctx), azapi.ErrCancelNotSupported) for the stacks scope.
  • L5 — lastState hoisted outside the loop but only read within one inner block — inline it.
  • L6 — erminalToOutcome has no doc comment; peers do.
  • L7 — Mixed American/British spelling across one feature: "Cancelling", "Cancellation", vs ARM's Canceled / Canceling and existing sentinel ErrDeploymentCanceledByUser. Pick American to match ARM + codebase.
  • L8 — Stray blank line mid-|| chain in cmd/middleware/error.go:89.
  • L9 — Follow-up: no gRPC hook for extensions to register interrupt handlers. Not blocking this PR, but worth a tracking issue — today extensions running long ops can't opt into graceful cancel.

Regression audit

Verified clean:

  • pkg/ux/internal/input.go prompt SIGINT — independent signal.Notify channel, coexists correctly
  • �xtensions/azure.ai.agents — separate process group
  • Hooks, gRPC extensions, monitor, deploy, package — no PushInterruptHandler callers; pre-PR behavior preserved

Flying blind (no regression tests):

  • Hooks SIGINT forwarding on Unix — signal.Notify(os.Interrupt) now consumes the terminal-generated SIGINT before the kernel sends it to the process group. Child hook processes may no longer receive SIGINT when the user presses Ctrl+C during hook execution.
  • �zd up UX consistency — provision phase gets the new prompt; deploy phase still hard-exits. Inconsistent across one command.
  • SIGTERM — new handler stack covers SIGINT only. kill bypasses all this. Worth documenting explicitly.

Excellent feature overall. H1–H4 are the blockers. M-findings are a mix of architectural consistency (aligns with the direction #7797 is taking) and the test coverage that your "industry-standard" framing deserves. Happy to discuss any of these.

@azure-sdk
Copy link
Copy Markdown
Collaborator

Azure Dev CLI Install Instructions

Install scripts

MacOS/Linux

May elevate using sudo on some platforms and configurations

bash:

curl -fsSL https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/7795/uninstall-azd.sh | bash;
curl -fsSL https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/7795/install-azd.sh | bash -s -- --base-url https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/7795 --version '' --verbose --skip-verify

pwsh:

Invoke-RestMethod 'https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/7795/uninstall-azd.ps1' -OutFile uninstall-azd.ps1; ./uninstall-azd.ps1
Invoke-RestMethod 'https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/7795/install-azd.ps1' -OutFile install-azd.ps1; ./install-azd.ps1 -BaseUrl 'https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/7795' -Version '' -SkipVerify -Verbose

Windows

PowerShell install

powershell -c "Set-ExecutionPolicy Bypass Process; irm 'https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/7795/uninstall-azd.ps1' > uninstall-azd.ps1; ./uninstall-azd.ps1;"
powershell -c "Set-ExecutionPolicy Bypass Process; irm 'https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/7795/install-azd.ps1' > install-azd.ps1; ./install-azd.ps1 -BaseUrl 'https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/7795' -Version '' -SkipVerify -Verbose;"

MSI install

powershell -c "irm 'https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/7795/azd-windows-amd64.msi' -OutFile azd-windows-amd64.msi; msiexec /i azd-windows-amd64.msi /qn"

Standalone Binary

MSI

Documentation

learn.microsoft.com documentation

title: Azure Developer CLI reference
description: This article explains the syntax and parameters for the various Azure Developer CLI commands.
author: alexwolfmsft
ms.author: alexwolf
ms.date: 04/20/2026
ms.service: azure-dev-cli
ms.topic: conceptual
ms.custom: devx-track-azdevcli

Azure Developer CLI reference

This article explains the syntax and parameters for the various Azure Developer CLI commands.

azd

The Azure Developer CLI (azd) is an open-source tool that helps onboard and manage your project on Azure

Options

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
      --docs                 Opens the documentation for azd in your web browser.
  -e, --environment string   The name of the environment to use.
  -h, --help                 Gets help for azd.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

  • azd add: Add a component to your project.
  • azd auth: Authenticate with Azure.
  • azd completion: Generate shell completion scripts.
  • azd config: Manage azd configurations (ex: default Azure subscription, location).
  • azd copilot: Manage GitHub Copilot agent settings. (Preview)
  • azd deploy: Deploy your project code to Azure.
  • azd down: Delete your project's Azure resources.
  • azd env: Manage environments (ex: default environment, environment variables).
  • azd extension: Manage azd extensions.
  • azd hooks: Develop, test and run hooks for a project.
  • azd infra: Manage your Infrastructure as Code (IaC).
  • azd init: Initialize a new application.
  • azd mcp: Manage Model Context Protocol (MCP) server. (Alpha)
  • azd monitor: Monitor a deployed project.
  • azd package: Packages the project's code to be deployed to Azure.
  • azd pipeline: Manage and configure your deployment pipelines.
  • azd provision: Provision Azure resources for your project.
  • azd publish: Publish a service to a container registry.
  • azd restore: Restores the project's dependencies.
  • azd show: Display information about your project and its resources.
  • azd template: Find and view template details.
  • azd up: Provision and deploy your project to Azure with a single command.
  • azd update: Updates azd to the latest version.
  • azd version: Print the version number of Azure Developer CLI.

azd add

Add a component to your project.

azd add [flags]

Options

      --docs   Opens the documentation for azd add in your web browser.
  -h, --help   Gets help for add.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd auth

Authenticate with Azure.

Options

      --docs   Opens the documentation for azd auth in your web browser.
  -h, --help   Gets help for auth.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd auth login

Log in to Azure.

Synopsis

Log in to Azure.

When run without any arguments, log in interactively using a browser. To log in using a device code, pass
--use-device-code.

To log in as a service principal, pass --client-id and --tenant-id as well as one of: --client-secret,
--client-certificate, or --federated-credential-provider.

To log in using a managed identity, pass --managed-identity, which will use the system assigned managed identity.
To use a user assigned managed identity, pass --client-id in addition to --managed-identity with the client id of
the user assigned managed identity you wish to use.

azd auth login [flags]

Options

      --check-status                           Checks the log-in status instead of logging in.
      --client-certificate string              The path to the client certificate for the service principal to authenticate with.
      --client-id string                       The client id for the service principal to authenticate with.
      --client-secret string                   The client secret for the service principal to authenticate with. Set to the empty string to read the value from the console.
      --docs                                   Opens the documentation for azd auth login in your web browser.
      --federated-credential-provider string   The provider to use to acquire a federated token to authenticate with. Supported values: github, azure-pipelines, oidc
  -h, --help                                   Gets help for login.
      --managed-identity                       Use a managed identity to authenticate.
      --redirect-port int                      Choose the port to be used as part of the redirect URI during interactive login.
      --tenant-id string                       The tenant id or domain name to authenticate with.
      --use-device-code[=true]                 When true, log in by using a device code instead of a browser.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd auth logout

Log out of Azure.

Synopsis

Log out of Azure

azd auth logout [flags]

Options

      --docs   Opens the documentation for azd auth logout in your web browser.
  -h, --help   Gets help for logout.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd auth status

Show the current authentication status.

Synopsis

Display whether you are logged in to Azure and the associated account information.

azd auth status [flags]

Options

      --docs   Opens the documentation for azd auth status in your web browser.
  -h, --help   Gets help for status.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd completion

Generate shell completion scripts.

Synopsis

Generate shell completion scripts for azd.

The completion command allows you to generate autocompletion scripts for your shell,
currently supports bash, zsh, fish and PowerShell.

See each sub-command's help for details on how to use the generated script.

Options

      --docs   Opens the documentation for azd completion in your web browser.
  -h, --help   Gets help for completion.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd completion bash

Generate bash completion script.

azd completion bash

Options

      --docs   Opens the documentation for azd completion bash in your web browser.
  -h, --help   Gets help for bash.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd completion fig

Generate Fig autocomplete spec.

azd completion fig

Options

      --docs   Opens the documentation for azd completion fig in your web browser.
  -h, --help   Gets help for fig.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd completion fish

Generate fish completion script.

azd completion fish

Options

      --docs   Opens the documentation for azd completion fish in your web browser.
  -h, --help   Gets help for fish.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd completion powershell

Generate PowerShell completion script.

azd completion powershell

Options

      --docs   Opens the documentation for azd completion powershell in your web browser.
  -h, --help   Gets help for powershell.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd completion zsh

Generate zsh completion script.

azd completion zsh

Options

      --docs   Opens the documentation for azd completion zsh in your web browser.
  -h, --help   Gets help for zsh.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd config

Manage azd configurations (ex: default Azure subscription, location).

Synopsis

Manage the Azure Developer CLI user configuration, which includes your default Azure subscription and location.

Available since azure-dev-cli_0.4.0-beta.1.

The easiest way to configure azd for the first time is to run azd init. The subscription and location you select will be stored in the config.json file located in the config directory. To configure azd anytime afterwards, you'll use azd config set.

The default value of the config directory is:

  • $HOME/.azd on Linux and macOS
  • %USERPROFILE%.azd on Windows

The configuration directory can be overridden by specifying a path in the AZD_CONFIG_DIR environment variable.

Options

      --docs   Opens the documentation for azd config in your web browser.
  -h, --help   Gets help for config.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd config get

Gets a configuration.

Synopsis

Gets a configuration in the configuration path.

The default value of the config directory is:

  • $HOME/.azd on Linux and macOS
  • %USERPROFILE%\.azd on Windows

The configuration directory can be overridden by specifying a path in the AZD_CONFIG_DIR environment variable.

azd config get <path> [flags]

Options

      --docs   Opens the documentation for azd config get in your web browser.
  -h, --help   Gets help for get.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd config list-alpha

Display the list of available features in alpha stage.

azd config list-alpha [flags]

Options

      --docs   Opens the documentation for azd config list-alpha in your web browser.
  -h, --help   Gets help for list-alpha.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd config options

List all available configuration settings.

Synopsis

List all possible configuration settings that can be set with azd, including descriptions and allowed values.

azd config options [flags]

Options

      --docs   Opens the documentation for azd config options in your web browser.
  -h, --help   Gets help for options.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd config reset

Resets configuration to default.

Synopsis

Resets all configuration in the configuration path.

The default value of the config directory is:

  • $HOME/.azd on Linux and macOS
  • %USERPROFILE%\.azd on Windows

The configuration directory can be overridden by specifying a path in the AZD_CONFIG_DIR environment variable to the default.

azd config reset [flags]

Options

      --docs    Opens the documentation for azd config reset in your web browser.
  -f, --force   Force reset without confirmation.
  -h, --help    Gets help for reset.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd config set

Sets a configuration.

Synopsis

Sets a configuration in the configuration path.

The default value of the config directory is:

  • $HOME/.azd on Linux and macOS
  • %USERPROFILE%\.azd on Windows

The configuration directory can be overridden by specifying a path in the AZD_CONFIG_DIR environment variable.

azd config set <path> <value> [flags]

Examples

azd config set defaults.subscription <yourSubscriptionID>
azd config set defaults.location eastus

Options

      --docs   Opens the documentation for azd config set in your web browser.
  -h, --help   Gets help for set.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd config show

Show all the configuration values.

Synopsis

Show all configuration values in the configuration path.

The default value of the config directory is:

  • $HOME/.azd on Linux and macOS
  • %USERPROFILE%\.azd on Windows

The configuration directory can be overridden by specifying a path in the AZD_CONFIG_DIR environment variable.

azd config show [flags]

Options

      --docs   Opens the documentation for azd config show in your web browser.
  -h, --help   Gets help for show.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd config unset

Unsets a configuration.

Synopsis

Removes a configuration in the configuration path.

The default value of the config directory is:

  • $HOME/.azd on Linux and macOS
  • %USERPROFILE%\.azd on Windows

The configuration directory can be overridden by specifying a path in the AZD_CONFIG_DIR environment variable.

azd config unset <path> [flags]

Examples

azd config unset defaults.location

Options

      --docs   Opens the documentation for azd config unset in your web browser.
  -h, --help   Gets help for unset.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd copilot

Manage GitHub Copilot agent settings. (Preview)

Options

      --docs   Opens the documentation for azd copilot in your web browser.
  -h, --help   Gets help for copilot.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd copilot consent

Manage tool consent.

Synopsis

Manage consent rules for tool execution.

Options

      --docs   Opens the documentation for azd copilot consent in your web browser.
  -h, --help   Gets help for consent.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd copilot consent grant

Grant consent trust rules.

Synopsis

Grant trust rules for tools and servers.

This command creates consent rules that allow tools to execute
without prompting for permission. You can specify different permission
levels and scopes for the rules.

Examples:

Grant always permission to all tools globally

azd copilot consent grant --global --permission always

Grant project permission to a specific tool with read-only scope

azd copilot consent grant --server my-server --tool my-tool --permission project --scope read-only

azd copilot consent grant [flags]

Options

      --action string       Action type: 'all' or 'readonly' (default "all")
      --docs                Opens the documentation for azd copilot consent grant in your web browser.
      --global              Apply globally to all servers
  -h, --help                Gets help for grant.
      --operation string    Operation type: 'tool' or 'sampling' (default "tool")
      --permission string   Permission: 'allow', 'deny', or 'prompt' (default "allow")
      --scope string        Rule scope: 'global', or 'project' (default "global")
      --server string       Server name
      --tool string         Specific tool name (requires --server)

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd copilot consent list

List consent rules.

Synopsis

List all consent rules for tools.

azd copilot consent list [flags]

Options

      --action string       Action type to filter by (all, readonly)
      --docs                Opens the documentation for azd copilot consent list in your web browser.
  -h, --help                Gets help for list.
      --operation string    Operation to filter by (tool, sampling)
      --permission string   Permission to filter by (allow, deny, prompt)
      --scope string        Consent scope to filter by (global, project). If not specified, lists rules from all scopes.
      --target string       Specific target to operate on (server/tool format)

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd copilot consent revoke

Revoke consent rules.

Synopsis

Revoke consent rules for tools.

azd copilot consent revoke [flags]

Options

      --action string       Action type to filter by (all, readonly)
      --docs                Opens the documentation for azd copilot consent revoke in your web browser.
  -h, --help                Gets help for revoke.
      --operation string    Operation to filter by (tool, sampling)
      --permission string   Permission to filter by (allow, deny, prompt)
      --scope string        Consent scope to filter by (global, project). If not specified, revokes rules from all scopes.
      --target string       Specific target to operate on (server/tool format)

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd deploy

Deploy your project code to Azure.

azd deploy <service> [flags]

Options

      --all                   Deploys all services that are listed in azure.yaml
      --docs                  Opens the documentation for azd deploy in your web browser.
  -e, --environment string    The name of the environment to use.
      --from-package string   Deploys the packaged service located at the provided path. Supports zipped file packages (file path) or container images (image tag).
  -h, --help                  Gets help for deploy.
      --timeout int           Maximum time in seconds for azd to wait for each service deployment. This stops azd from waiting but does not cancel the Azure-side deployment. (default: 1200) (default 1200)

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd down

Delete your project's Azure resources.

azd down [<layer>] [flags]

Options

      --docs                 Opens the documentation for azd down in your web browser.
  -e, --environment string   The name of the environment to use.
      --force                Does not require confirmation before it deletes resources.
  -h, --help                 Gets help for down.
      --purge                Does not require confirmation before it permanently deletes resources that are soft-deleted by default (for example, key vaults).

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd env

Manage environments (ex: default environment, environment variables).

Options

      --docs   Opens the documentation for azd env in your web browser.
  -h, --help   Gets help for env.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd env config

Manage environment configuration (ex: stored in .azure//config.json).

Options

      --docs   Opens the documentation for azd env config in your web browser.
  -h, --help   Gets help for config.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd env config get

Gets a configuration value from the environment.

Synopsis

Gets a configuration value from the environment's config.json file.

azd env config get <path> [flags]

Options

      --docs                 Opens the documentation for azd env config get in your web browser.
  -e, --environment string   The name of the environment to use.
  -h, --help                 Gets help for get.

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd env config set

Sets a configuration value in the environment.

Synopsis

Sets a configuration value in the environment's config.json file.

Values are automatically parsed as JSON types when possible. Booleans (true/false),
numbers (42, 3.14), arrays ([...]), and objects ({...}) are stored with their native
JSON types. Plain text values are stored as strings. To force a JSON-typed value to be
stored as a string, wrap it in JSON quotes (e.g. '"true"' or '"8080"').

azd env config set <path> <value> [flags]

Examples

azd env config set myapp.endpoint https://example.com
azd env config set myapp.debug true
azd env config set myapp.count 42
azd env config set infra.parameters.tags '{"env":"dev"}'
azd env config set myapp.port '"8080"'

Options

      --docs                 Opens the documentation for azd env config set in your web browser.
  -e, --environment string   The name of the environment to use.
  -h, --help                 Gets help for set.

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd env config unset

Unsets a configuration value in the environment.

Synopsis

Removes a configuration value from the environment's config.json file.

azd env config unset <path> [flags]

Examples

azd env config unset myapp.endpoint

Options

      --docs                 Opens the documentation for azd env config unset in your web browser.
  -e, --environment string   The name of the environment to use.
  -h, --help                 Gets help for unset.

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd env get-value

Get specific environment value.

azd env get-value <keyName> [flags]

Options

      --docs                 Opens the documentation for azd env get-value in your web browser.
  -e, --environment string   The name of the environment to use.
  -h, --help                 Gets help for get-value.

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

  • azd env: Manage environments (ex: default environment, environment variables).
  • Back to top

azd env get-values

Get all environment values.

azd env get-values [flags]

Options

      --docs                 Opens the documentation for azd env get-values in your web browser.
  -e, --environment string   The name of the environment to use.
  -h, --help                 Gets help for get-values.

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

  • azd env: Manage environments (ex: default environment, environment variables).
  • Back to top

azd env list

List environments.

azd env list [flags]

Options

      --docs   Opens the documentation for azd env list in your web browser.
  -h, --help   Gets help for list.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

  • azd env: Manage environments (ex: default environment, environment variables).
  • Back to top

azd env new

Create a new environment and set it as the default.

azd env new <environment> [flags]

Options

      --docs                  Opens the documentation for azd env new in your web browser.
  -h, --help                  Gets help for new.
  -l, --location string       Azure location for the new environment
      --subscription string   ID of an Azure subscription to use for the new environment

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

  • azd env: Manage environments (ex: default environment, environment variables).
  • Back to top

azd env refresh

Refresh environment values by using information from a previous infrastructure provision.

azd env refresh <environment> [flags]

Options

      --docs                 Opens the documentation for azd env refresh in your web browser.
  -e, --environment string   The name of the environment to use.
  -h, --help                 Gets help for refresh.
      --hint string          Hint to help identify the environment to refresh
      --layer string         Provisioning layer to refresh the environment from.

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

  • azd env: Manage environments (ex: default environment, environment variables).
  • Back to top

azd env remove

Remove an environment.

azd env remove <environment> [flags]

Options

      --docs                 Opens the documentation for azd env remove in your web browser.
  -e, --environment string   The name of the environment to use.
      --force                Skips confirmation before performing removal.
  -h, --help                 Gets help for remove.

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

  • azd env: Manage environments (ex: default environment, environment variables).
  • Back to top

azd env select

Set the default environment.

azd env select [<environment>] [flags]

Options

      --docs   Opens the documentation for azd env select in your web browser.
  -h, --help   Gets help for select.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

  • azd env: Manage environments (ex: default environment, environment variables).
  • Back to top

azd env set

Set one or more environment values.

Synopsis

Set one or more environment values using key-value pairs or by loading from a .env formatted file.

azd env set [<key> <value>] | [<key>=<value> ...] | [--file <filepath>] [flags]

Options

      --docs                 Opens the documentation for azd env set in your web browser.
  -e, --environment string   The name of the environment to use.
      --file string          Path to .env formatted file to load environment values from.
  -h, --help                 Gets help for set.

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

  • azd env: Manage environments (ex: default environment, environment variables).
  • Back to top

azd env set-secret

Set a name as a reference to a Key Vault secret in the environment.

Synopsis

You can either create a new Key Vault secret or select an existing one.
The provided name is the key for the .env file which holds the secret reference to the Key Vault secret.

azd env set-secret <name> [flags]

Options

      --docs                 Opens the documentation for azd env set-secret in your web browser.
  -e, --environment string   The name of the environment to use.
  -h, --help                 Gets help for set-secret.

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

  • azd env: Manage environments (ex: default environment, environment variables).
  • Back to top

azd extension

Manage azd extensions.

Options

      --docs   Opens the documentation for azd extension in your web browser.
  -h, --help   Gets help for extension.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd extension install

Installs specified extensions.

azd extension install <extension-id> [flags]

Options

      --docs             Opens the documentation for azd extension install in your web browser.
  -f, --force            Force installation, including downgrades and reinstalls
  -h, --help             Gets help for install.
  -s, --source string    The extension source to use for installs
  -v, --version string   The version of the extension to install

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd extension list

List available extensions.

azd extension list [--installed] [flags]

Options

      --docs            Opens the documentation for azd extension list in your web browser.
  -h, --help            Gets help for list.
      --installed       List installed extensions
      --source string   Filter extensions by source
      --tags strings    Filter extensions by tags

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd extension show

Show details for a specific extension.

azd extension show <extension-id> [flags]

Options

      --docs            Opens the documentation for azd extension show in your web browser.
  -h, --help            Gets help for show.
  -s, --source string   The extension source to use.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd extension source

View and manage extension sources

Options

      --docs   Opens the documentation for azd extension source in your web browser.
  -h, --help   Gets help for source.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd extension source add

Add an extension source with the specified name

azd extension source add [flags]

Options

      --docs              Opens the documentation for azd extension source add in your web browser.
  -h, --help              Gets help for add.
  -l, --location string   The location of the extension source
  -n, --name string       The name of the extension source
  -t, --type string       The type of the extension source. Supported types are 'file' and 'url'

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd extension source list

List extension sources

azd extension source list [flags]

Options

      --docs   Opens the documentation for azd extension source list in your web browser.
  -h, --help   Gets help for list.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd extension source remove

Remove an extension source with the specified name

azd extension source remove <name> [flags]

Options

      --docs   Opens the documentation for azd extension source remove in your web browser.
  -h, --help   Gets help for remove.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd extension source validate

Validate an extension source's registry.json file.

Synopsis

Validate an extension source's registry.json file.

Accepts a source name (from 'azd extension source list'), a local file path,
or a URL. Checks required fields, valid capabilities, semver version format,
platform artifact structure, and extension ID format.

azd extension source validate <name-or-path-or-url> [flags]

Options

      --docs     Opens the documentation for azd extension source validate in your web browser.
  -h, --help     Gets help for validate.
      --strict   Enable strict validation (require checksums)

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd extension uninstall

Uninstall specified extensions.

azd extension uninstall [extension-id] [flags]

Options

      --all    Uninstall all installed extensions
      --docs   Opens the documentation for azd extension uninstall in your web browser.
  -h, --help   Gets help for uninstall.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd extension upgrade

Upgrade specified extensions.

azd extension upgrade [extension-id] [flags]

Options

      --all              Upgrade all installed extensions
      --docs             Opens the documentation for azd extension upgrade in your web browser.
  -h, --help             Gets help for upgrade.
  -s, --source string    The extension source to use for upgrades
  -v, --version string   The version of the extension to upgrade to

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd hooks

Develop, test and run hooks for a project.

Options

      --docs   Opens the documentation for azd hooks in your web browser.
  -h, --help   Gets help for hooks.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd hooks run

Runs the specified hook for the project, provisioning layers, and services

azd hooks run <name> [flags]

Options

      --docs                 Opens the documentation for azd hooks run in your web browser.
  -e, --environment string   The name of the environment to use.
  -h, --help                 Gets help for run.
      --layer string         Only runs hooks for the specified provisioning layer.
      --platform string      Forces hooks to run for the specified platform.
      --service string       Only runs hooks for the specified service.

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd infra

Manage your Infrastructure as Code (IaC).

Options

      --docs   Opens the documentation for azd infra in your web browser.
  -h, --help   Gets help for infra.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd infra generate

Write IaC for your project to disk, allowing you to manually manage it.

azd infra generate [flags]

Options

      --docs                 Opens the documentation for azd infra generate in your web browser.
  -e, --environment string   The name of the environment to use.
      --force                Overwrite any existing files without prompting
  -h, --help                 Gets help for generate.

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd init

Initialize a new application.

Synopsis

Initialize a new application.

When used with --template, a new directory is created (named after the template)
and the project is initialized inside it — similar to git clone.
Pass "." as the directory to initialize in the current directory instead.

azd init [flags]

Options

  -b, --branch string         The template branch to initialize from. Must be used with a template argument (--template or -t).
      --docs                  Opens the documentation for azd init in your web browser.
  -e, --environment string    The name of the environment to use.
  -f, --filter strings        The tag(s) used to filter template results. Supports comma-separated values.
      --from-code             Initializes a new application from your existing code.
  -h, --help                  Gets help for init.
  -l, --location string       Azure location for the new environment
  -m, --minimal               Initializes a minimal project.
  -s, --subscription string   ID of an Azure subscription to use for the new environment
  -t, --template string       Initializes a new application from a template. You can use a Full URI, <owner>/<repository>, <repository> if it's part of the azure-samples organization, or a local directory path (./dir, ../dir, or absolute path).
      --up                    Provision and deploy to Azure after initializing the project from a template.

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd mcp

Manage Model Context Protocol (MCP) server. (Alpha)

Options

      --docs   Opens the documentation for azd mcp in your web browser.
  -h, --help   Gets help for mcp.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd mcp start

Starts the MCP server.

Synopsis

Starts the Model Context Protocol (MCP) server.

This command starts an MCP server that can be used by MCP clients to access
azd functionality through the Model Context Protocol interface.

azd mcp start [flags]

Options

      --docs   Opens the documentation for azd mcp start in your web browser.
  -h, --help   Gets help for start.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd monitor

Monitor a deployed project.

azd monitor [flags]

Options

      --docs                 Opens the documentation for azd monitor in your web browser.
  -e, --environment string   The name of the environment to use.
  -h, --help                 Gets help for monitor.
      --live                 Open a browser to Application Insights Live Metrics. Live Metrics is currently not supported for Python apps.
      --logs                 Open a browser to Application Insights Logs.
      --overview             Open a browser to Application Insights Overview Dashboard.

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd package

Packages the project's code to be deployed to Azure.

azd package <service> [flags]

Options

      --all                  Packages all services that are listed in azure.yaml
      --docs                 Opens the documentation for azd package in your web browser.
  -e, --environment string   The name of the environment to use.
  -h, --help                 Gets help for package.
      --output-path string   File or folder path where the generated packages will be saved.

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd pipeline

Manage and configure your deployment pipelines.

Options

      --docs   Opens the documentation for azd pipeline in your web browser.
  -h, --help   Gets help for pipeline.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd pipeline config

Configure your deployment pipeline to connect securely to Azure. (Beta)

azd pipeline config [flags]

Options

  -m, --applicationServiceManagementReference string   Service Management Reference. References application or service contact information from a Service or Asset Management database. This value must be a Universally Unique Identifier (UUID). You can set this value globally by running azd config set pipeline.config.applicationServiceManagementReference <UUID>.
      --auth-type string                               The authentication type used between the pipeline provider and Azure for deployment (Only valid for GitHub provider). Valid values: federated, client-credentials.
      --docs                                           Opens the documentation for azd pipeline config in your web browser.
  -e, --environment string                             The name of the environment to use.
  -h, --help                                           Gets help for config.
      --principal-id string                            The client id of the service principal to use to grant access to Azure resources as part of the pipeline.
      --principal-name string                          The name of the service principal to use to grant access to Azure resources as part of the pipeline.
      --principal-role stringArray                     The roles to assign to the service principal. By default the service principal will be granted the Contributor and User Access Administrator roles. (default [Contributor,User Access Administrator])
      --provider string                                The pipeline provider to use (github for Github Actions and azdo for Azure Pipelines).
      --remote-name string                             The name of the git remote to configure the pipeline to run on. (default "origin")

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd provision

Provision Azure resources for your project.

azd provision [<layer>] [flags]

Options

      --docs                  Opens the documentation for azd provision in your web browser.
  -e, --environment string    The name of the environment to use.
  -h, --help                  Gets help for provision.
  -l, --location string       Azure location for the new environment
      --no-state              (Bicep only) Forces a fresh deployment based on current Bicep template files, ignoring any stored deployment state.
      --preview               Preview changes to Azure resources.
      --subscription string   ID of an Azure subscription to use for the new environment

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd publish

Publish a service to a container registry.

azd publish <service> [flags]

Options

      --all                   Publishes all services that are listed in azure.yaml
      --docs                  Opens the documentation for azd publish in your web browser.
  -e, --environment string    The name of the environment to use.
      --from-package string   Publishes the service from a container image (image tag).
  -h, --help                  Gets help for publish.
      --to string             The target container image in the form '[registry/]repository[:tag]' to publish to.

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd restore

Restores the project's dependencies.

azd restore <service> [flags]

Options

      --all                  Restores all services that are listed in azure.yaml
      --docs                 Opens the documentation for azd restore in your web browser.
  -e, --environment string   The name of the environment to use.
  -h, --help                 Gets help for restore.

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd show

Display information about your project and its resources.

azd show [resource-name|resource-id] [flags]

Options

      --docs                 Opens the documentation for azd show in your web browser.
  -e, --environment string   The name of the environment to use.
  -h, --help                 Gets help for show.
      --show-secrets         Unmask secrets in output.

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd template

Find and view template details.

Options

      --docs   Opens the documentation for azd template in your web browser.
  -h, --help   Gets help for template.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd template list

Show list of sample azd templates. (Beta)

azd template list [flags]

Options

      --docs             Opens the documentation for azd template list in your web browser.
  -f, --filter strings   The tag(s) used to filter template results. Supports comma-separated values.
  -h, --help             Gets help for list.
  -s, --source string    Filters templates by source.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd template show

Show details for a given template. (Beta)

azd template show <template> [flags]

Options

      --docs   Opens the documentation for azd template show in your web browser.
  -h, --help   Gets help for show.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd template source

View and manage template sources. (Beta)

Options

      --docs   Opens the documentation for azd template source in your web browser.
  -h, --help   Gets help for source.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd template source add

Adds an azd template source with the specified key. (Beta)

Synopsis

The key can be any value that uniquely identifies the template source, with well-known values being:
・default: Default templates
・awesome-azd: Templates from https://aka.ms/awesome-azd

azd template source add <key> [flags]

Options

      --docs              Opens the documentation for azd template source add in your web browser.
  -h, --help              Gets help for add.
  -l, --location string   Location of the template source. Required when using type flag.
  -n, --name string       Display name of the template source.
  -t, --type string       Kind of the template source. Supported types are 'file', 'url' and 'gh'.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd template source list

Lists the configured azd template sources. (Beta)

azd template source list [flags]

Options

      --docs   Opens the documentation for azd template source list in your web browser.
  -h, --help   Gets help for list.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd template source remove

Removes the specified azd template source (Beta)

azd template source remove <key> [flags]

Options

      --docs   Opens the documentation for azd template source remove in your web browser.
  -h, --help   Gets help for remove.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd up

Provision and deploy your project to Azure with a single command.

azd up [flags]

Options

      --docs                  Opens the documentation for azd up in your web browser.
  -e, --environment string    The name of the environment to use.
  -h, --help                  Gets help for up.
  -l, --location string       Azure location for the new environment
      --subscription string   ID of an Azure subscription to use for the new environment

Options inherited from parent commands

  -C, --cwd string   Sets the current working directory.
      --debug        Enables debugging and diagnostics logging.
      --no-prompt    Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd update

Updates azd to the latest version.

azd update [flags]

Options

      --channel string             Update channel: stable or daily.
      --check-interval-hours int   Override the update check interval in hours.
      --docs                       Opens the documentation for azd update in your web browser.
  -h, --help                       Gets help for update.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

azd version

Print the version number of Azure Developer CLI.

azd version [flags]

Options

      --docs   Opens the documentation for azd version in your web browser.
  -h, --help   Gets help for version.

Options inherited from parent commands

  -C, --cwd string           Sets the current working directory.
      --debug                Enables debugging and diagnostics logging.
  -e, --environment string   The name of the environment to use.
      --no-prompt            Accepts the default value instead of prompting, or it fails if there is no default.

See also

Copy link
Copy Markdown
Member

@jongio jongio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three items I didn't see covered elsewhere in the existing thread.

N1 [high] - race between deploy-success and the interrupt handler goroutine

In watchTerminalInterrupt the handler now runs on a spawned goroutine (pkg/input/console.go:1003), but BicepProvider.Deploy only does a non-blocking read of interruptCh after cleanupOnce():

cleanupOnce() // pops handler, cancels deployCtx. Doesn't wait for an in-flight handler.
select {
case outcome := <-interruptCh:
    ...
default: // handler hasn't written yet? we take this path.
}

If SIGINT arrives in the narrow window where deployModule has just returned successfully, the signal goroutine can capture the handler via currentInterruptHandler() before cleanupOnce pops, then spawn the handler while the main thread races past, takes the default branch, and returns success. The handler goroutine now shows the interrupt prompt over success output, and if the user picks "Cancel" it calls ARM Cancel on a deployment that already succeeded.

This is separate from the "second Ctrl+C is dropped" concern in H3, it's a single Ctrl+C arriving at the wrong moment. cleanupOnce needs to either wait for any in-flight handler to drain, or the handler closure needs a "deploy already completed" guard before calling runInterruptPrompt.

N2 [info] - quick check on H2's premise

On this branch finishInterruptHandler() is deferred at pkg/input/console.go:1004, and the handler runs on a spawned goroutine, so for range signalChan keeps draining even if the handler panics. Unhandled goroutine panics crash the whole process by Go's default behavior, so "process unkillable" doesn't match the runtime semantics here. Adding recover() inside the handler goroutine is still reasonable defense-in-depth, but worth verifying the failure modes before refactoring on H2's described premise.

N3 [low] - test isolation in interrupt_test.go

TestPushInterruptHandler_LIFO and TestTryStartInterruptHandler_PreventsConcurrent mutate interruptStack and interruptRunning (package globals) but don't reset them via t.Cleanup. If a future test panics before calling the returned pop() or the deferred finishInterruptHandler, residual state leaks into subsequent tests. Complements M10's test-coverage gap, and the broker abstraction from H4 would eliminate this class of fragility outright.

Fixes from wbreza's review:
- H1/N1: Race between deploy-success and interrupt handler — replaced
  select-based check with atomic CAS state machine (deployStateRunning →
  deployStateInterrupting or deployStateCompleted) so the handler and
  Deploy goroutine never conflict.
- H2: Panic in interrupt handler — added recover() with stack trace
  logging in watchTerminalInterrupt so a handler panic doesn't leave the
  process unkillable.
- H3: Second Ctrl+C force-exit — added forceExitPending counter in
  interrupt.go; second suppressed Ctrl+C while a handler is running
  triggers os.Exit(130) matching POSIX convention (kubectl, terraform).
- M13: terminalToOutcome now a BicepProvider method with ctx as first
  parameter per AGENTS.md convention.
- L7: Spelling consistency — 'Cancelling' → 'Canceling' to match ARM
  API and codebase convention.
- L8: Removed stray blank line in cmd/middleware/error.go.

Fixes from jongio's review:
- N1: Same race fix as H1 above (CAS state machine).
- N2: Panic recovery (defense-in-depth per Jon's suggestion).
- N3: Test cleanup — added t.Cleanup() for PushInterruptHandler pops
  and finishInterruptHandler to prevent global state leaks on assertion
  failure.

Fixes from Copilot bot review:
- Unbounded Get call in cancel-request error path — added
  context.WithTimeout wrapper (30s).
- DeploymentUrl fetch in prompt — added timeout to prevent indefinite
  blocking on slow/unreachable ARM.
- Deleted state mismatch — added explicit case in terminalToOutcome for
  DeploymentProvisioningStateDeleted.
- Test cleanup in interrupt_test.go (same as N3 above).

New tests:
- TestForceExitCounter: validates force-exit on 2nd suppressed Ctrl+C.
- TestForceExitCounter_ResetsOnNewHandler: ensures counter resets
  between handler lifecycles.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@vhvb1989 vhvb1989 requested review from jongio and wbreza April 22, 2026 22:31
@vhvb1989
Copy link
Copy Markdown
Member Author

Review feedback addressed

@wbreza @jongio — pushed a commit addressing the blocking and actionable findings from both reviews. Here is the breakdown:

Addressed in this push

Finding Fix
H1 (wbreza) + N1 (jongio) — deploy-success vs handler race Replaced select/default with an atomic.Int32 CAS state machine (running → interrupting vs running → completed). Handler and Deploy atomically compete — no TOCTOU window.
H2 (wbreza) + N2 (jongio) — handler panic Added recover() with runtime.Stack in watchTerminalInterrupt. Panicked handler falls through to os.Exit(1) instead of leaving the process unkillable. (Per Jon's N2 clarification: the handler runs inline so a panic crashes the process by default — recover is defense-in-depth.)
H3 (wbreza) — 2nd Ctrl+C silently dropped Added forceExitPending flag reset on each handler lifecycle. Second suppressed Ctrl+C while a handler is running triggers os.Exit(130) (128+SIGINT), matching the convention used by kubectl, terraform, docker, etc.
N3 (jongio) — test cleanup leak Added t.Cleanup() for all PushInterruptHandler pops and finishInterruptHandler calls so global state never leaks on assertion failure.
M13ctx as last parameter terminalToOutcome is now a method on BicepProvider with ctx as first parameter per AGENTS.md convention.
L6 — missing doc comment Added doc comment on terminalToOutcome.
L7 — spelling "Cancelling" → "Canceling" to match ARM API and codebase convention.
L8 — stray blank line Removed.
Copilot bot — unbounded Get/DeploymentUrl Added context.WithTimeout wrappers (30s) on both the fallback deployment.Get and the DeploymentUrl fetch in the prompt path.
Copilot botDeleted state mismatch Added explicit DeploymentProvisioningStateDeleted case in terminalToOutcome.

New tests

  • TestForceExitCounter — validates force-exit triggers on 2nd suppressed Ctrl+C
  • TestForceExitCounter_ResetsOnNewHandler — ensures counter resets between handler lifecycles

Deferred to follow-up (architectural)

  • H4 (IoC broker) — significant refactor; better as a separate PR
  • M1–M4 (API surface consolidation, InterruptDecider interface) — architectural, not blocking correctness
  • M5–M12 (telemetry coverage, additional test scenarios, env var escape hatch) — incremental improvements
  • L1–L5, L9 — low priority refinements and follow-up tracking issues

All tests pass (go test ./pkg/input/... ./pkg/infra/... ./cmd/middleware/... ./internal/cmd/... -short), lint and cspell clean. Ready for re-review when you get a chance.

Comment on lines +189 to +206
if portalUrl != "" {
p.console.Message(ctx,
output.WithHighLightFormat("The Azure deployment will continue running. Track it here:\n %s",
portalUrl))
}
return interruptOutcome{
err: provisioning.ErrDeploymentInterruptedLeaveRunning,
telemetryValue: "leave_running",
}
}

switch choice {
case 0: // leave running
if portalUrl != "" {
p.console.Message(ctx,
output.WithHighLightFormat("The Azure deployment will continue running. Track it here:\n %s",
portalUrl))
}
Copy link
Copy Markdown
Contributor

@JeffreyCA JeffreyCA Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we format the URL using output.WithLinkFormat? Minor: also it's kind of duplicated above:

Image

Comment on lines +338 to +341
p.console.Message(ctx,
output.WithHighLightFormat(
"Canceled deployment is recorded in the portal:\n %s", portalUrl))
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here (URL formatting)

Copy link
Copy Markdown
Contributor

@JeffreyCA JeffreyCA Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this cancel nested deployments? I tried Ctrl+C to cancel the deployment, which completed. But when ran azd provision it failed with:

  (x) Failed: Validating deployment

ERROR: deployment failed: error deploying infrastructure: validating deployment to subscription: 

Validation Error Details:
DeploymentActive: The deployment with resource id '/subscriptions/<id>/resourceGroups/microsoft-rg/providers/Microsoft.Resources/deployments/ai-project' cannot be saved, because this would overwrite an existing deployment which is still active. If this is an intentional re-deployment, please wait for the original deployment to complete, or cancel it before re-attempting the operation. The previous deployment was started at '4/23/2026 12:19:18 AM' with correlationId '<id>', and will expire at '4/30/2026 12:19:18 AM' if it does not complete before then. Please see https://aka.ms/arm-deploy-resources for usage details.

It turned out there was a nested deployment still in progress and I had to manually cancel it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should cancel the top level deployment which should have fall back to cancel all nested children. It should just take some time to propagate, but I will double check this expectation. Thanks

Copy link
Copy Markdown
Contributor

@JeffreyCA JeffreyCA Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed when I Ctrl+C while it's trying to cancel the deployment, it ignores/swallows them, which is a bit inconsistent with the changes made earlier to treat the second Ctrl+C as force-exit during the prompt.

Any thoughts on if we should allow users to force-exit during this window as well? I guess the deployment could be in an unknown state (could be cancelled or not).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In comments above folks were in favor of not swallowing the second cancellation. Initially I wanted to swallow it b/c I usually press ctr+c like a maniac to stop a process 😆, so I would probably miss the change to ask azd to cancel.
But I updated that. I don't have a strong opinion,

Copy link
Copy Markdown
Member

@jongio jongio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addresses my prior feedback. Verified the new commit (bea4593...199a887).

The deployState atomic CAS approach correctly closes the race from N1. Both the handler and Deploy atomically compete for state ownership from deployStateRunning - clean lock-free coordination with no window for both sides to proceed. Panic recovery (N2) and t.Cleanup registrations (N3) also look correct.

Force-exit on third Ctrl+C (first=handled, second=suppressed, third=exit) is a good addition. More forgiving than strict POSIX convention (kubectl/terraform exit on 2nd total press), which prevents accidental double-press from killing the process during the cancel flow.

+1 on @JeffreyCA's output.WithLinkFormat suggestion for portal URLs. provisioning_progress_display.go:107 already uses it for deployment links, and the style guide lists WithLinkFormat as the convention for URLs.

if !tryStartInterruptHandler() {
// A handler is already running. A second Ctrl+C while a
// handler is active is treated as a force-exit (standard
// POSIX convention: kubectl, terraform, docker, etc.).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this comment says "A second Ctrl+C" but the actual behavior is the third total press triggers force-exit (first=handler starts, second=suppressed/pending, third=exit). The comment in interrupt.go correctly says "second suppressed press". Consider aligning the wording here so readers don't expect 2nd-press exit behavior.

Copy link
Copy Markdown
Contributor

@wbreza wbreza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed after addressing feedback (commit 199a887). Verified the original high-priority findings:

Resolved ✅

H1 (Race condition) — Atomic CAS state machine (\deployStateRunning\ → \Interrupting/\Completed) properly eliminates the race between signal handler and deploy completion. Both sides compete via \CompareAndSwap; the loser waits on channels. Clean fix.

H2 (Panic recovery) — \defer recover()\ with
untime.Stack()\ logging wraps handler execution. \ inishInterruptHandler()\ always runs to reset state, ensuring the process remains killable.

H3 (Force-exit on 2nd Ctrl+C) — \ orceExitPending\ counter with \os.Exit(130)\ on second suppressed press. Counter resets on new handler start. Follows POSIX convention correctly.

Remaining ⚠️

H4 (Process-global interrupt state) — \interruptStack, \interruptMu, \interruptRunning, \ orceExitPending\ remain package-global variables. No \InterruptBroker\ interface or IoC injection. This means parallel tests collide on shared state and multi-console hosts would leak handlers across sessions. Acceptable for the single-process CLI today, but should be tracked as a follow-up to avoid issues when adding parallel test coverage or multi-session support.

New observations

  • Test coverage gap: Core interrupt flow (
    unInterruptPrompt, \cancelAndAwaitTerminal, \installDeploymentInterruptHandler) has no unit tests — only the utility functions are covered. Targeted tests with faked \console.Select()\ and \deployment.Cancel()/\Get()\ would significantly reduce risk.
  • Minor: In \cancelAndAwaitTerminal, when the cancel API fails and the fallback \Get()\ also fails, \getErr\ is silently discarded. A \log.Printf\ for that error would improve production diagnosability.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support provision-cancelation

6 participants