atlantis stop: interrupt the currently running plan/apply
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.
- I'd be willing to implement this feature (contributing guide)
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 → SIGKILLTwo 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
Stoppingand a human decides whether to escalate. Permission: same asplan. - 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-unlockneeded) and resources may exist that were never recorded in state. Permission: same asapply, and additionally gated behind a server flag.
Design outline:
- Kill the process, not the goroutine. Cancellation propagates a real
context.Contextdown 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/ForceStoppedvs generic failure). - Context plumbing.
RunCommandWithVersiontakescommand.ProjectContext(not acontext.Context) and spawns via plainexec.Command(server/core/terraform/tfclient/terraform_client.go), so today nothing can reach the child process. Acontext.Contextneeds to be threaded from the command runner through the step runners to the terraform client.exec.CommandContextalone is not enough (it SIGKILLs): the child runs in its own process group (Setpgid) with a small supervisor that owns signal delivery, so customrunsteps 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.
stopis PR-wide by design — no-d/-wscoping. 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 cancelremains the queue-only tool. - State machine. New project statuses
Stopping→Stopped/ForceStopped, so re-planning after a stop behaves, commit statuses don't hang, andstopwhileStoppingis a no-op (whilestop --forceescalates). - 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
stoponly 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 cancelwith a--runningflag: 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.
Source: runatlantis/atlantis