#6842·atlantis

atlantis stop: interrupt the currently running plan/apply

Author: gian25-workCreated Sep 4, 2026Updated Sep 4, 2026

Community Note

  • Please vote on this issue by adding a reaction to the original issue to help the community and maintainers prioritize this request. Searching for pre-existing feature requests helps us consolidate datapoints for identical requirements into a single place, thank you!
  • Please do not leave "+1" or other comments that do not add relevant new information or questions, they generate extra noise for issue followers and do not help prioritize the request.
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment.

Describe the user story

As an operator, when a plan or apply is running against the wrong branch, a huge module, or a hung provider, I want to interrupt it from the PR — today I can only wait for it to finish or restart the Atlantis pod, which kills every other run on the server and can leave orphaned state locks.

This was the original ask in #187, but that discussion converged on cancelling queued commands, which shipped as atlantis cancel. The docs now state explicitly: "There is currently no mechanism in Atlantis to interrupt the currently running process." This issue proposes that mechanism.

Describe the solution you'd like

A new comment command (new verb, so atlantis cancel queue semantics stay untouched):

atlantis stop           # safe: SIGINT only, no escalation
atlantis stop --force   # SIGINT → SIGINT (tf force-abort) → grace → SIGKILL

Two explicit tiers, because interruption safety differs fundamentally:

  • Safe stop (default): send SIGINT to the terraform process group once, then wait. Terraform handles SIGINT gracefully: stops scheduling new operations, finishes in-flight provider calls, persists state, releases the state lock. No timer, no escalation — if a hung provider never exits, the project stays visibly in Stopping and a human decides whether to escalate. Permission: same as plan.
  • Force stop (--force): escalation ladder with one rung per grace period — second SIGINT (terraform's own force-abort, which still best-effort releases the state lock), then SIGKILL to the process group. The result comment must warn that the state lock may be orphaned (terraform force-unlock needed) and resources may exist that were never recorded in state. Permission: same as apply, and additionally gated behind a server flag.

Design outline:

  • Kill the process, not the goroutine. Cancellation propagates a real context.Context down the call stack; the child process dies, the step runner returns an error, and the existing unwind path (working-dir lock release, commit status, PR comment) is reused as-is. Only the exit-cause label differs (Stopped / ForceStopped vs generic failure).
  • Context plumbing. RunCommandWithVersion takes command.ProjectContext (not a context.Context) and spawns via plain exec.Command (server/core/terraform/tfclient/terraform_client.go), so today nothing can reach the child process. A context.Context needs to be threaded from the command runner through the step runners to the terraform client. exec.CommandContext alone is not enough (it SIGKILLs): the child runs in its own process group (Setpgid) with a small supervisor that owns signal delivery, so custom run steps and provider subprocesses are covered too.
  • Run registry. In-memory map keyed by repo/pull/project/workspace holding the supervisor handle; registered at project-command start, deregistered on exit. Same single-instance scope as the existing command queue.
  • Queue interaction. stop is PR-wide by design — no -d/-w scoping. It drains all of the PR's queued commands first, then signals every running process for the PR. Draining before signalling matters: otherwise a queued apply starts the instant the running command dies. Keeping it unscoped avoids the footgun of stopping one project while another project's queued apply proceeds; atlantis cancel remains the queue-only tool.
  • State machine. New project statuses StoppingStopped / ForceStopped, so re-planning after a stop behaves, commit statuses don't hang, and stop while Stopping is a no-op (while stop --force escalates).
  • Server flags: --enable-stop-command (off by default), --allow-force-stop, --force-stop-grace-period (default 30s).

Suggested phasing to keep PRs reviewable: (1) context plumbing, pure refactor; (2) plan-only stop — zero state risk, immediate value on long plans; (3) apply stop, safe tier; (4) --force.

Describe the drawbacks of your solution

  • The context plumbing touches the whole command-execution call stack — it's the expensive part and the reason #187 stalled. Phase 1 is a large, behavior-neutral refactor that needs careful review.
  • Force-stopping an apply can orphan the terraform state lock and leave provisioned resources unrecorded in state. Mitigated by: off by default, separate server flag, apply-level permissions, and an explicit warning comment — but the residual risk is inherent to SIGKILL and cannot be engineered away.
  • The run registry is in-memory, so stop only works on the instance running the command — consistent with the existing queue, but one more thing that doesn't transfer to a future HA story.
  • Process groups/SIGINT are Unix semantics; on Windows the ladder degrades (no clean SIGINT equivalent). Realistically: full support on Unix, best-effort kill on Windows.
  • A stopped run leaves a partially initialized working dir; the next plan re-inits, but users may be surprised by the extra init time.

Describe alternatives you've considered

  • Extending atlantis cancel with a --running flag: rejected to keep the existing queue-only semantics unambiguous and avoid changing the behavior of a shipped command; a separate verb also lets the two commands carry different permission levels.
  • Restarting the Atlantis pod: works today but kills all runs server-wide, loses the queue, and can orphan state locks — strictly worse than a targeted SIGINT.
  • Server-side execution timeouts (existing per-command timeout flags): complementary, but they can't cover the "human realizes the plan is wrong 10 seconds in" case, and a timeout firing mid-apply has the same safety problem without a human choosing the risk tier.
  • Automatic escalation in the safe tier (SIGINT, then SIGKILL after a timeout): rejected — it silently converts a safe operation into a dangerous one; escalation should be an explicit human decision.

This proposal was drafted with AI assistance (Claude); the design and the text were reviewed by the author.