https://www.runatlantis.io/ logo
Join Slack
Powered by
# github-issues
  • g

    GitHub

    09/03/2026, 11:01 AM
    #6837 Parameterize the repeated test cases before they get copied again Issue created by krewenki There are a few places where separate test functions call the same helper with the same arguments. The tests aren't exposing a regression and I don't think the assertions are wrong. This is mostly a DRY cleanup before the next variation gets added by copying another wrapper. I found nine clusters: • apply runner: three identical targeted plan-in-flight cases, two identical stale-base automerge cases, and two identical ignored-target cases (examples) • DB updater: two names for the same same-head/different-base failure case (tests and here) • project command builder: two identical no-plan/empty-status cases (tests) • project command runner: three identical plan-mutation cases and two identical stale-command classification cases (mutation cases, classification cases) • Bitbucket Cloud and Bitbucket Server each have two identity tests that call the same helper with no different setup (Cloud, Server) Some of the names describe slightly different intentions, but because the bodies and inputs are the same they aren't actually separate coverage. If the shared helper changes, all of the named scenarios still move together. I think we should parameterize the cases where there are real input variations, and collapse the aliases where there aren't. That'll make it more obvious what behavior is distinct and give the next test case a natural place to add its actual input instead of another copied function. runatlantis/atlantis
  • g

    GitHub

    09/03/2026, 5:52 PM
    #4218 `atlantis testdrive` doesn't work due to ngrok requiring an account Issue created by KevinCarterDev 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. --- Overview of the Issue ngrok requires an account and the client needs to authenticate with an authtoken. Reproduction Steps
    Copy code
    % ./atlantis testdrive
    Welcome to Atlantis testdrive!
    
    This mode sets up Atlantis on a test repo so you can try it out. We will
    - fork an example terraform project to your username
    - install terraform (if not already in your PATH)
    - install ngrok so we can expose Atlantis to GitHub
    - start Atlantis
    
    Press Ctrl-c at any time to exit
    
    
    <http://github.com|github.com> username: KevinCarterDev
    
    To continue, we need you to create a GitHub personal access token
    with "repo" scope so we can fork an example terraform project.
    
    Follow these instructions to create a token (we don't store any tokens):
    <https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token#creating-a-fine-grained-personal-access-token>
    - use "atlantis" for the token description
    - add "repo" scope
    - copy the access token
    
    GitHub access token (will be hidden):
    => forking repo
    => fork completed!
    => terraform found in $PATH at /opt/homebrew/bin/terraform
    => ngrok found in $PATH at /opt/homebrew/bin/ngrok
    => creating secure tunnel
    Error: creating ngrok tunnel: timeout, logs:
    t=2024-02-07T11:39:51-0700 lvl=info msg="open config file" path=/var/folders/pz/1s31jdrx0fz4gbxgjhkvxmhc0000gp/T/atlantis-testdrive-ngrok-config290247732 err=nil
    t=2024-02-07T11:39:51-0700 lvl=info msg="starting web service" obj=web addr=localhost:41414 allow_hosts=[]
    t=2024-02-07T11:39:52-0700 lvl=eror msg="failed to reconnect session" obj=tunnels.session obj=csess id=8627fe3d2a25 err="authentication failed: Usage of ngrok requires a verified account and authtoken
    .\n\nSign up for an account: <https://dashboard.ngrok.com/signup>\nInstall your authtoken: <https://dashboard.ngrok.com/get-started/your-authtoken\r>\n\r\nERR_NGROK_4018\r\n"
    t=2024-02-07T11:39:52-0700 lvl=eror msg="session closing" obj=tunnels.session err="authentication failed: Usage of ngrok requires a verified account and authtoken.\n\nSign up for an account: https://d
    <http://ashboard.ngrok.com/signup|ashboard.ngrok.com/signup>\nInstall your authtoken: <https://dashboard.ngrok.com/get-started/your-authtoken\r>\n\r\nERR_NGROK_4018\r\n"
    t=2024-02-07T11:39:52-0700 lvl=info msg="received stop request" obj=app stopReq="{err:{Remote:true Inner:{Inner:0x140005b72c0}} restart:false}"
    t=2024-02-07T11:39:52-0700 lvl=eror msg="terminating with error" obj=app err="authentication failed: Usage of ngrok requires a verified account and authtoken.\n\nSign up for an account: <https://dashbo>
    <http://ard.ngrok.com/signup|ard.ngrok.com/signup>\nInstall your authtoken: <https://dashboard.ngrok.com/get-started/your-authtoken\r>\n\r\nERR_NGROK_4018\r\n"
    t=2024-02-07T11:39:52-0700 lvl=crit msg="command failed" err="authentication failed: Usage of ngrok requires a verified account and authtoken.\n\nSign up for an account: <https://dashboard.ngrok.com/si>
    gnup\nInstall your authtoken: <https://dashboard.ngrok.com/get-started/your-authtoken\r>\n\r\nERR_NGROK_4018\r\n"
    
    ERROR:  authentication failed: Usage of ngrok requires a verified account and authtoken.
    ERROR:
    ERROR:  Sign up for an account: <https://dashboard.ngrok.com/signup>
    ERROR:  Install your authtoken: <https://dashboard.ngrok.com/get-started/your-authtoken>
    ERROR:
    ERROR:  ERR_NGROK_4018
    ERROR:
    
    
    Usage:
      atlantis testdrive [flags]
    
    Flags:
      -h, --help   help for testdrive
    Logs Environment details
    Copy code
    % ./atlantis version
    atlantis 0.27.1 (commit: da67e7d) (build date: 2024-01-21T23:42:15Z)
    
    % ngrok -v
    ngrok version 3.5.0
    Additional Context Manually running the command that testdrive runs results in the same error ``` % ngrok start atlantis --config /var/folders/pz/1s31jdrx0fz4gbxgjhkvxmhc0000gp/T/atlantis-testdrive-ngrok-config290247732 --log stderr --log-format term INFO[02-07|114054] open config file path=/var/folders/pz/1s31jdrx0fz4gbxgjhkvxmhc0000gp/T/atlantis-testdrive-ngrok-config290247732 err=nil INFO[02-07|114054] starting web service obj=web addr=localhost:41414 allow_hosts=[] EROR[02-07|114054] failed to reconnect session obj=tunnels.session obj=csess id=b5a7b057bd2d err="authentication failed: Usage of ngrok requires a verified account and authtoken.\n\nSig n up for an account: https://dashboard.ngrok.com/signup\nInstall your authtoken: https://dashboard.ngrok.com/get-started/your-authtoken\r\n\r\nERR_NGROK_4018\r\n" EROR[02-07|114054] session closing obj=tunnels.session err="authentication failed: Usage of ngrok requires a verified account and authtoken.\n\nSign up for an account: https ://dashboard.ngrok.com/signup\nInstall your authtoken: https://dashboard.ngrok.com/get-started/your-authtoken\r\n\r\nERR_NGROK_4018\r\n" INFO[02-07|114054] received stop request obj=app stopReq="{err{Remotetrue Inner{Inner0x140006064d0}} restart:false}" EROR[02-07|114054] terminating with error obj=app err="authentication failed: Usage of ngrok requires a verified account and authtoken.\n\nSign up for an account: https://dashboard .ngrok.com/signup\nInstall your authtoken: https://dashboard.ngrok.com/get-started/your-authtoken\r\n\r\nERR_NGROK_4018\r\n" WARN[02-07|114054] failed to check for update obj=updater err="Post \"https://update.equinox.io/check\": context canceled" CRIT[02-07|114054] command failed err="authentication failed: Usage of ngrok requires a verified account and authtoken.\n\nSign up for an account: https://dashboard.ngrok.c om/signup\nInstall your authtoken: https://dashboard.ngrok.com/get-started/your-authtoken\r\n\r\nERR_NGROK_4018\r\n" INFO[02-07|114054] no more state changes obj=tunnels.session NAME: start - start tunnels by name from the configuration file USAGE: ngrok start [flags] DESCRIPTION: Starts tunnels by name from the configuration file. You may specify any number of tunnel names. You may start all tunnels in the configuration file with the --all switch. TERMS OF SERVICE: https://ngrok.com/tos EXAMPLES: ngrok start dev # start tunnel named 'dev' in the configuration file ngrok start web blog # start tunnels named 'web' and 'blog' ngrok start --all # start all tunnels defined in the config file OPTIONS: --all start all tunnels in the configuration file --authtoken string ngrok.com authtoken identifying a user --config strings path to config files; they are merged if multiple -h, --help help for start --log string path to log file, 'stdout', 'stderr' or 'false' (default "false") --log-format string log record format: 'term', 'logfmt', 'json' (default "term") --log-level string logging level: 'debug', 'info', 'warn', 'error', 'crit' (default "info") --none start running no tunnels ERROR: authentication failed: Usage of ngrok requires a verified account and authtoken. ERROR: ERROR: Sign up f… runatlantis/atlantis
    • 1
    • 1
  • g

    GitHub

    09/04/2026, 11:21 AM
    #6842 atlantis stop: interrupt the currently running plan/apply Issue created by gian25-work 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):
    Copy code
    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
    Stopping
    →
    Stopped
    /
    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. runatlantis/atlantis
  • g

    GitHub

    09/04/2026, 5:32 PM
    #6843 Parallel Workspace Applies Fail since 0.46.0 Issue created by benfielden-onx 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. --- Overview of the Issue Since v0.46.0 (specifically #6605), pull-level apply lock are enforced for every apply, targeted or not. Two
    atlantis apply -w <workspace>
    comments on the same PR can no longer run in parallel:
    Copy code
    cannot run "apply": pull request 22 is currently locked by "apply" for commit 12345678.
    Wait until the previous command is complete and try again
    On previous versions locks look to have been keyed by repo/pull/workspace/path/project. That means targeted applies for different workspaces were permitted. Our workflow is to have one repo with multiple workspaces each representing an enviornment/cluster. In many cases we want to apply multiple at once (daily-1, daily-2) while waiting to apply additional environments (integration-1, integration-2). We use one command per workspace because we want each environment to have its own comment and job link and to be independently re-runnable when one fails. To that end we also set --disable-apply-all to avoid someone applying all clusters at once without an initial smoke deploy. Reproduction Steps 1. A repo with a single root module and two or more workspaces, and an
    atlantis.yaml
    with
    parallel_apply: true
    and one project per workspace all sharing
    dir: ./
    . 2. Push a commit that touches a shared
    .tf
    so autoplan plans every workspace. Let it finish. 3. Comment
    atlantis apply -w daily-1
    . 4. Within a few seconds, comment
    atlantis apply -w daily-2
    . 5. The second command is rejected with the error above. On v0.45.0 both applies run concurrently. Logs Apply Error comment on the PR cannot run "apply": pull request 22 is currently locked by "apply" for commit a1b2c3d. Wait until the previous command is complete and try again The first atlantis apply -w daily-1 was commented at 135017Z and completed at 135807Z. The second, atlantis apply -w daily-2, was commented at 135023Z and rejected at 135027Z. Environment details • Atlantis version: v0.47.1 • Deployment method: Kubernetes StatefulSet running the official ghcr.io/runatlantis/atlantis image • Atlantis flags: --disable-apply-all=true, --parallel-pool-size=25, --automerge=true, --default-tf-distribution=opentofu, --gh-allow-mergeable-bypass-apply=true Sever-side repo config: Atlantis server-side config file (plan steps elided): repos: - id: /.*/ branch: /(main|master)/ apply_requirements: [mergeable] plan_requirements: [undiverged] pre_workflow_hooks: - run: sh ./generate-atlantis-yaml.sh && cat ./atlantis.yaml description: Generate dynamic atlantis.yaml commands: plan workflows: default: apply: steps: - run: tofu workspace select -no-color $WORKSPACE - run: tofu apply -parallelism=100 -no-color $PLANFILE 2>&1 | tee ./apply.out - run: sh ./rollout-check.sh - run: sh ./annotate-grafana.sh Repo
    atlantis.yaml
    version: 3 parallel_plan: true parallel_apply: true projects: - dir: ./ autoplan: when_modified: ["./**/*","!./**/*md","!./*tfvars","./daily-1.tfvars"] workspace: daily-1 - dir: ./ autoplan: when_modified: ["./**/*","!./**/*md","!./*tfvars","./daily-2.tfvars"] workspace: daily-2 - dir: ./ autoplan: when_modified: ["./**/*","!./**/*md","!./*tfvars","./integration-1.tfvars"] workspace: integration-1 - dir: ./ autoplan: when_modified: ["./**/*","!./**/*md","!./*tfvars","./integration-2.tfvars"] workspace: integration-2 Additional Context • #6786 appears to hit the same lock in a slightly different way: multi-instance -p targeting and --silence-no-projects. • Our apply workflow is custom run steps with no built-in apply step, because we need tofu workspace select, a rollout check and a Grafana annotation around the apply. The Custom Workflow Boundary section of #6605 says those steps are outside Atlantis-managed apply-step validation, so we are serialized by a lock whose purpose is protection we dont get. #6781 carved custom workflows out of the managed-plan-file requirement for the same reason. runatlantis/atlantis
  • g

    GitHub

    09/08/2026, 10:36 AM
    #6574 feature: Begin publishing `-slim` image variants. Issue created by lachlankidson 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 it stands there is no way for consumers to reduce the size of the Atlantis images. To take the
    -debian
    variant as an example, the size of the image (per dive) is around 1GB:
    dive ghcr.io/runatlantis/atlantis:v0.44.0-debian
    Image name: ghcr.io/runatlantis/atlantis:v0.44.0-debian Total Image size: 1.0 GB Potential wasted space: 4.4 MB Image efficiency score: 99 % The vast majority of the image size is coming from two layers: •
    sha256:d5c57c40bdff313840546756e697df0bc204b82320f66856ca25304f578b61b1
    •
    sha256:8ffae1a96e8b31a362813c0515e96bf3d739b516732d68d6dcb0da8ae2959549
    Copy code
    469 MB  COPY /usr/local/bin/terraform/terraform* /usr/local/bin/ # buildkit
    211 MB  COPY /usr/local/bin/tofu/tofu* /usr/local/bin/ # buildkit
    i.e. https://github.com/runatlantis/atlantis/blob/main/Dockerfile#L181-L182, with these directories being populated by https://github.com/runatlantis/atlantis/blob/main/Dockerfile#L152-L161 This approach presents a few problems: • Users who only use Tofu acquire 469MB of unnecessary terraform dependencies. • Users who only use Terraform acquire 211MB of unnecessary tofu dependencies. • Users who build their own images on top of Atlantis or who rely on Atlantis downloading specific versions at runtime potentially acquire 680MB of unnecessary dependencies. • Some of the versions being acquired have known CVEs: #6569 (comment) • Whilst a custom docker image using atlantis as a base could remove these versions, they cannot reduce the size as the two layers will still exist in final image. Given that atlantis also publishes a helm chart (https://github.com/runatlantis/helm-charts/) it is reasonable to expect that many users will be deploying via Kubernetes, where image size will impact the time to pull the container image and thus present scaling challenges. Describe the solution you'd like I see two potential solutions. • Reduce size by simply only installing one default version of terraform and tofu. • Publish
    -slim
    variants for both existing images where neither terraform nor tofu are installed. The second option could be achieved by passing in a new build-arg and using it as a conditional. The layer would not be removed but instead would be empty on the
    -slim
    images, i.e. something similar to:
    Copy code
    RUN if [ "$SLIM_BUILD" = "false"] ; then (./download-release.sh \
            "terraform" \
            "${TARGETPLATFORM}" \
            "${DEFAULT_TERRAFORM_VERSION}" \
            "1.8.5 1.9.8 1.10.5 ${DEFAULT_TERRAFORM_VERSION}" \
        && ./download-release.sh \
            "tofu" \
            "${TARGETPLATFORM}" \
            "${DEFAULT_OPENTOFU_VERSION}" \
            "${DEFAULT_OPENTOFU_VERSION}") ; fi
    Describe the drawbacks of your solution • Increased build complexity. • Doubles CI compute for building images. • If no terraform/tofu versions are present on the image there is no fallback versions for issues like an upstream outage (nowhere to download terraform from) or key expiry (i.e. #6405) Describe alternatives you've considered • Building a custom atlantis image by copying and adapting the Dockerfile directly. • Creating a minimal OCI image via something like nix and including the atlantis package runatlantis/atlantis
    • 1
    • 1
  • g

    GitHub

    09/08/2026, 11:03 AM
    #6621 Drop shipping end-of-life terraform versions with no security support Issue created by adampie Atlantis v0.46.0 ships with EOL terraform versions which no longer receive security support and contain a bunch of CVEs. Support Data from endoflife.date | Release | Released | Security Support | Latest | | ------- | ----------- | ------------------------------------------- | ------ | | 1.10 | 26 Nov 2024 | Ended 1 year and 1 months ago (14 May 2025) | 1.10.5 | | 1.9 | 26 Jun 2024 | Ended 1 year and 4 months ago (27 Feb 2025) | 1.9.8 | | 1.8 | 10 Apr 2024 | Ended 1 year and 7 months ago (26 Nov 2024) | 1.8.5 | CVEs Data from Wiz scan scoped to
    /usr/local/bin/terraform...
    | Release | Critical | High | Medium | Low | | ------- | -------- | ---- | ------ | --- | | 1.10.5 | 10 | 8 | 9 | 2 | | 1.9.8 | 10 | 9 | 9 | 3 | | 1.8.5 | 10 | 9 | 12 | 3 | Proposal Remove bundling legacy terraform versions part of Atlantis docker image runatlantis/atlantis
    • 1
    • 1
  • g

    GitHub

    09/09/2026, 2:29 PM
    #6871 GitLab: a third-party commit-status poster strands Atlantis's own external pipeline in `running` forever Issue created by bronto-rikstv 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. --- Overview of the Issue We run Atlantis on a GitLab project where a second integration (CodeRabbit in our case) also posts commit statuses. Whenever both post on the same commit, Atlantis creates an
    external
    pipeline for its first status write and then abandons it: the pipeline sits at
    running
    forever, even after the MR is merged. When we went looking, we found seven of these orphans on one repo, the oldest from May 2024: this had been happening quietly for over a year before anyone noticed. The mechanism is in
    UpdateStatus
    in
    server/events/vcs/gitlab/client.go
    (same code at v0.47.1 and on current
    main
    ): 1. It fetches the commit, and if
    commit.LastPipeline
    is set, posts the status with
    PipelineID = commit.LastPipeline.ID
    . 2. Otherwise it retries the lookup a couple of times, then falls back to
    Ref = pull.HeadBranch
    , the short branch name, which makes GitLab find-or-create a pipeline. Nothing checks that
    commit.LastPipeline
    is the pipeline Atlantis itself created. So if another integration creates a newer pipeline on the same commit between Atlantis's first and second writes, Atlantis silently switches to it, and the pipeline it created never reaches a terminal state. The reason there are two pipelines at all: GitLab's set-commit-status API is find-or-create keyed on the
    ref
    string, and the two posters spell the same branch differently: Atlantis's fallback uses the bare branch name (
    <branch>
    ), CodeRabbit uses the fully-qualified
    refs/heads/<branch>
    . GitLab treats those as distinct refs and creates a pipeline for each. Reproduction Steps 1. A GitLab project with no CI jobs of its own, so no MR pipeline exists and Atlantis always takes the
    Ref = pull.HeadBranch
    fallback for its first write. 2. A second integration that posts commit statuses for the same commits using the fully-qualified
    refs/heads/<branch>
    ref. 3. Open an MR. Atlantis autoplans and posts
    atlantis/plan
    = running first, creating pipeline A keyed on the short ref. 4. The second integration posts its status a few seconds later, creating pipeline B keyed on
    refs/heads/<branch>
    . 5. Atlantis's subsequent writes find
    commit.LastPipeline
    = B and post there. 6. Pipeline A stays
    running
    forever, including after the MR is merged. For one incident we have the full timeline: Atlantis created pipeline A at 131036.517Z and wrote to it exactly once, 141 ms later. CodeRabbit created pipeline B at 131040.189Z, 3.7 s after Atlantis gave up looking, and B then received every one of Atlantis's remaining statuses, ending
    success
    at 132016.860Z. Plan and apply both succeeded and the MR merged normally; only the pipeline record is wrong. We could also watch the adoption happen live after enabling
    gitlab-status-retry-enabled
    (see Logs below): Atlantis retries, then logs that it found a pipeline and adopts it, but the pipeline it adopts was created by CodeRabbit, not by Atlantis. Logs The bug itself produces no errors: everything succeeds from Atlantis's point of view, so there is nothing to show for the failure case. What we can show is the adoption decision. With
    gitlab-status-retry-enabled: true
    , on a commit where CodeRabbit's pipeline appeared while Atlantis was still retrying: Logs
    Copy code
    No pipeline found for commit e19727f35c9d1265febf6e601644c37a00dec2be, retrying in 2s
    No pipeline found for commit e19727f35c9d1265febf6e601644c37a00dec2be, retrying in 3.4194057s
    Pipeline found for commit e19727f35c9d1265febf6e601644c37a00dec2be, setting pipeline ID to 237707
    Pipeline 237707 was created by the other integration. Atlantis attaches all its statuses to it by ID from then on. In this instance that happens to be harmless (one pipeline, one poster driving it to terminal state), but it is the same unchecked adoption that strands Atlantis's own pipeline in the race described above. One more practical consequence for anyone else cleaning these up: the stranded pipelines contain no jobs, so GitLab's stuck/timeout handling never touches them, and
    POST /pipelines/:id/cancel
    returns 200 but does not change their status. The only way we found to clear them was
    DELETE /pipelines/:id
    as Owner. Environment details • Atlantis v0.47.1 (`ghcr.io/runatlantis/atlantis:v0.47.1-debian`), single instance on ECS Fargate, EFS-backed data dir • GitLab self-managed 19.3.1 • Terragrunt workflow;
    atlantis.yaml
    generated per run by a pre-workflow hook;
    --parallel=false
    •
    gitlab-status-retry-enabled
    was
    false
    (the default) when the orphans were created; we have since enabled it Additional Context • This looks like the same underlying defect as #5228 (still open): both cases come from adopting
    commit.LastPipeline.ID
    without checking that the pipeline belongs to this branch or to Atlantis. #5228's trigger is two MRs sharing a HEAD commit; ours is a third-party status poster, which we didn't find covered in any existing issue. • #5944 / PR #5986 (the
    gitlab-status-retry-enabled
    flag) helps but doesn't close the hole: the longer wait makes it likely Atlantis finds the other pipeline before ever creating its own, so the window is narrowed, not removed, and when it does find one, it still adopts it without checking whose it is. • A durable fix would be to check the ref and/or owner of
    commit.LastPipeline
    before adopting it, or to have Atlantis remember and reuse the pipeline it created for a given commit. Happy to test a patch against a live GitLab 19.3.1 setup. runatlantis/atlantis
  • g

    GitHub

    09/10/2026, 2:33 PM
    #6872 merge checkout strategy: mergeAgain fails with `git reset --hard refs/remotes/origin/&lt;base&gt;` after a PR is retargeted to a new base branch Issue created by jjoos 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. --- Overview of the Issue With
    --checkout-strategy=merge
    and a persistent data volume, a PR whose base branch changes while its head commit stays the same wedges its working directory permanently. Every subsequent plan fails with:
    Copy code
    running git reset --hard refs/remotes/origin/main: fatal: ambiguous argument 'refs/remotes/origin/main': unknown revision or path not in the working tree.
    Use '--' to separate paths from revisions, like this:
    'git <command> [<revision>...] -- [<file>...]'
    : exit status 128
    This is the common "stacked PR" flow.
    feature/branch-b
    targets `feature/branch-a`;
    feature/branch-a
    merges and is deleted; the forge retargets the open PR onto
    main
    . (On GitLab this is automatic — it posts "deleted the
    feature/branch-a
    branch. This merge request now targets the
    main
    branch"
    . GitHub does the same retarget when a base branch is deleted, and a user can retarget manually on either forge.) Because the head commit never changed, the working dir is never re-cloned, and it only ever had a remote-tracking ref for the old base branch. Why the existing guard does not catch it #6146 / #6177 added
    remoteHasBranch
    so a changed base branch forces a re-clone. That guard lives in
    attemptReuseCloneDir
    and is unreachable in this scenario. Line numbers below are
    server/events/working_dir.go
    at v0.47.1. 1.
    Clone()
    fast path (L138–146) runs
    isBranchAtTargetRef(p.HeadCommit)
    . Under the merge strategy this compares
    HEAD^2
    , which is still the unchanged PR head, so it returns early:
    Copy code
    repo is at correct commit "<sha>" so will not re-clone (fast path)
    attemptReuseCloneDir
    — and therefore the
    remoteHasBranch
    guard at L185 — is never reached. Even in the slow path the guard only runs when
    isUpToDate == false
    . 2.
    MergeAgain()
    (L206) then calls
    recheckDiverged()
    (L265), whose
    git remote update
    fails because the old base branch was deleted from the remote:
    Copy code
    getting remote update failed: Fetching origin  
    fatal: couldn't find remote ref refs/heads/feature/branch-a  
    error: could not fetch origin
    Per the documented behaviour it returns
    true
    ("we prefer to assume divergence for safety"). 3.
    mergeAgain()
    (L814) unconditionally runs: w.wrappedGit(logger, c, "reset", "--hard", fmt.Sprintf("refs/remotes/origin/%s", c.pr.BaseBranch))
    refs/remotes/origin/main
    does not exist, because
    forceClone
    had cloned with
    --branch feature/branch-a --single-branch
    , leaving exactly one remote-tracking ref. It fails, and there is no
    remoteHasBranch
    check on this path and no fallback to
    forceClone
    . The state is not self-healing: every retry takes the same fast path and fails identically. The only recovery is deleting the working directory by hand. Confirmed on the affected checkout — note the single, stale refspec:
    Copy code
    $ git -C <data-dir>/repos/<org>/<repo>/123/default config --get-all remote.origin.fetch
    +refs/heads/feature/branch-a:refs/remotes/origin/feature/branch-a
    
    $ git -C <data-dir>/repos/<org>/<repo>/123/default reflog --date=iso
    <sha1> HEAD@{...}: merge <pr-head-sha>: Merge made by the 'ort' strategy.
    <sha2> HEAD@{...}: reset: moving to origin/feature/branch-a
    ...
    <sha3> HEAD@{...}: clone: from https://<host>/<org>/<repo>.git
    Reproduction Steps 1. Run Atlantis with
    --checkout-strategy=merge
    and a persistent data volume. 2. Open PR A (
    feature/branch-a
    →
    main
    ). 3. Open PR B (
    feature/branch-b
    →
    feature/branch-a
    ), stacked on A. 4. Let Atlantis plan PR B, so its working dir is cloned with
    --branch feature/branch-a --single-branch
    . 5. Merge PR A and delete
    feature/branch-a
    . The forge retargets PR B onto
    main
    . 6. Comment
    atlantis plan
    on PR B without pushing any new commit. PR B now fails with the
    git reset --hard refs/remotes/origin/main
    error above, and keeps failing on every retry. Logs Logs {"level":"info","caller":"events/working_dir.go:143","msg":"repo is at correct commit \"<pr-head-sha>\" so will not re-clone (fast path)","json":{"repo":"<org>/<repo>","pull":"123"}} {"level":"warn","caller":"events/working_dir.go:311","msg":"getting remote update failed: Fetching origin\nfatal: couldn't find remote ref refs/heads/feature/branch-a\nerror: could not fetch origin\nFetching source\nFrom https://<host>/<org>/<repo>\n <old>..<new> main -> source/main\n","json":{"repo":"<org>/<repo>","pull":"123"}} {"level":"info","caller":"events/working_dir.go:251","msg":"base branch may have been updated, using merge strategy and will merge again","json":{"repo":"<org>/<repo>","pull":"123"}} {"level":"error","caller":"events/project_command_runner.go","msg":"Error running plan operation: running git reset --hard refs/remotes/origin/main: fatal: ambiguous argument 'refs/remotes/origin/main': unknown revision or path not in the working tree.\nUse '--' to separate paths from revisions, like this:\n'git <command> [<revision>...] -- [<file>...]'\n: exit status 128","json":{"repo":"<org>/<repo>","pull":"123"}} Environment details • Atlantis version:
    v0.47.1
    • Deployment method: Helm chart
    6.9.2
    on Kubernetes, with a persistent volume for
    --data-dir
    • If not running the latest Atlantis version have you tried to reproduce this issue on the latest version: the relevant code in
    server/events/working_dir.go
    is identical on
    main
    at the time of writing, so it should still reproduce • Atlantis flags:
    --checkout-strategy=merge
    (via
    ATLANTIS_CHECKOUT_STRATEGY
    ), GitLab VCS Additional Context Suggested fix — make the base-branch check reachable on the paths that actually run for an unchanged head commit. Either or both of: • In
    mergeAgain()
    (or in
    MergeAgain()
    before it), check
    remoteHasBranch(c.pr.BaseBranch)
    and fall back to
    forceClone
    when the ref is missing, instead of resetting to a ref that cannot exist. • In the
    Clone()
    fast path (L138–146), also require
    remoteHasBranch(c.pr.BaseBranch)
    when
    w.CheckoutMerge
    is set, so a retargeted PR does not short-circuit past the existing guard. The first is the more complete fix, since
    MergeAgain()
    is reached even when
    Clone()
    legitimately reuses the directory. Related: #6146, #6177 (added
    remoteHasBranch
    to the
    Clone
    path). I am happy to open a PR for this if maintainers agree with the approach. runatlantis/atlantis
  • g

    GitHub

    09/15/2026, 2:23 PM
    #6905 Add ATLANTIS_ENABLE_AUTOPLAN_LABEL config option similar to ATLANTIS_DISABLE_AUTOPLAN_LABEL Issue created by leonnicolas 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 I don't want to autoplan PRs by default. When users explicitly want it, they can have their PRs autoplanned. Describe the solution you'd like There is already a label that users can set per PR to disable autoplan. I would like to have a label to enable autoplanning per PR. The label could be configured via env variable similar to
    ATLANTIS_DISABLE_AUTOPLAN_LABEL
    Copy code
    ATLANTIS_ENABLE_AUTOPLAN_LABEL=atlantis-autoplan
    and via the cli flag similar to
    --disable-autoplan-label
    Copy code
    --enable-autoplan-label=atlantis-autoplan
    When the project has autoplan enabled the label has no affect. Describe the drawbacks of your solution Confusion with enable and disable flags. Describe alternatives you've considered 1. Automatically label all PRs with the
    ATLANTIS_DISABLE_AUTOPLAN_LABEL
    value. race condition with atlantis 1. Users must always explicitly comment
    atlantis plan
    well, this is the status quo that I want to improve on runatlantis/atlantis
  • g

    GitHub

    09/16/2026, 3:31 PM
    #6906 Unexpected content-type when trying to download Terraform v1.16.3 in Atlantis v0.44.0 Issue created by danielfm 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. --- Overview of the Issue We are running this version for many weeks without issues, but today we started seeing Terraform plans failing with the following error:
    Copy code
    error downloading terraform version 1.16.3: unexpected content-type: binary/octet-stream (expected any of ["application/x-zip-compressed" "application/zip"]): Downloading terraform Version 1.16.3
    Reproduction Steps Submit a PR or run a plan as of now, without forcing a specific default TF version. Logs Logs
    Copy code
    {"level":"info","ts":"2026-09-16T14:58:51.395Z","caller":"tfclient/terraform_client.go:519","msg":"could not find terraform version 1.16.3 in PATH or /atlantis/bin","json":{"repo":"...","pull":"810"}}
    {"level":"info","ts":"2026-09-16T14:58:51.395Z","caller":"tfclient/terraform_client.go:521","msg":"downloading terraform version 1.16.3 from download URL <https://releases.hashicorp.com>","json":{"repo":"...","pull":"810"}}
    {"level":"info","ts":"2026-09-16T14:58:51.871Z","caller":"github/client.go:988","msg":"Updating GitHub Check status for 'atlantis/plan: ...' to 'failure'","json":{"repo":"...","pull":"810"}}
    {"level":"error","ts":"2026-09-16T14:58:52.620Z","caller":"events/instrumented_project_command_runner.go:81","msg":"Error running plan operation: error downloading terraform version 1.16.3: unexpected content-type: binary/octet-stream (expected any of [\"application/x-zip-compressed\" \"application/zip\"]): Downl
    oading terraform Version 1.16.3\n","json":{"repo":"...","pull":"810"},"stacktrace":"<http://github.com/runatlantis/atlantis/server/events.RunAndEmitStats|github.com/runatlantis/atlantis/server/events.RunAndEmitStats>\n\tgithub.com/runatlantis/atlantis/server/events/instrumented_project_command_runner.go:81\ngithub.com/runatlantis/atlantis/server/events
    .(*InstrumentedProjectCommandRunner).Plan\n\<http://tgithub.com/runatlantis/atlantis/server/events/instrumented_project_command_runner.go:41|tgithub.com/runatlantis/atlantis/server/events/instrumented_project_command_runner.go:41>\ngithub.com/runatlantis/atlantis/server/events.RunOneProjectCmd\n\tgithub.com/runatlantis/atlantis/server/events/project_command_pool_executor.go:22\ngithub.com/runatlantis/atlantis
    /server/events.runProjectCmds\n\<http://tgithub.com/runatlantis/atlantis/server/events/project_command_pool_executor.go:102|tgithub.com/runatlantis/atlantis/server/events/project_command_pool_executor.go:102>\ngithub.com/runatlantis/atlantis/server/events.runProjectCmdsWithCancellationTracker\n\tgithub.com/runatlantis/atlantis/server/events/project_command_pool_executor.go:178\ngithub.com/runatlantis/atl
    antis/server/events.(*PlanCommandRunner).runAutoplan\n\<http://tgithub.com/runatlantis/atlantis/server/events/plan_command_runner.go:156|tgithub.com/runatlantis/atlantis/server/events/plan_command_runner.go:156>\ngithub.com/runatlantis/atlantis/server/events.(*PlanCommandRunner).Run\n\tgithub.com/runatlantis/atlantis/server/events/plan_command_runner.go:325\ngithub.com/runatlantis/atlantis/serv
    er/events.(*DefaultCommandRunner).RunAutoplanCommand\n\<http://tgithub.com/runatlantis/atlantis/server/events/command_runner.go:242%22}|tgithub.com/runatlantis/atlantis/server/events/command_runner.go:242"}>
    Environment details • Atlantis version: v0.44.0 • Deployment method: GKE, directly, not using any module or chart • If not running the latest Atlantis version have you tried to reproduce this issue on the latest version: Did not try the latest version Additional Context Nothing. runatlantis/atlantis
    • 1
    • 1
  • g

    GitHub

    09/22/2026, 6:40 AM
    #5868 Reduce container image size of Atlantis Issue created by patrickdung 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 It is found that the container image of atlantis is quite large (around 1GB) and for us, we need to include terragrunt and more tools in the future. Here is a finding: Inside a container image 273987739 -rwxr-xr-x 1 root root 90579096 Apr 9 13:03 terraform 273987741 -rwxr-xr-x 1 root root 90579096 Apr 9 13:03 terraform1.11.4 Same content, but the i-node is different, so it is using extra storage (~90MB in here) I think it's the same case for opentofu (another ~87MB) Describe the solution you'd like Suggestions: 1. Use of softlink/hardlink for tofu/terraform to the latest version 2. or use of other de-duplicate/hardlink tools for tofu/terraform to the latest version 3. also consider reduce the no of terraform to ship to 3 or 2 versions instead of 4 versions 4. For Debian based container image, some packages are installed but I think they are not needed, like gcc-12-base openssh-server openssh-sftp-server 5. Finally, may be also consider container image squashing (buildx/buildah) to further reduce image size? Describe the drawbacks of your solution Describe alternatives you've considered runatlantis/atlantis
    • 1
    • 1
  • g

    GitHub

    09/24/2026, 1:15 PM
    #6922 Azure DevOps: --ignore-vcs-status-names is ignored in PullIsMergeable Issue created by emanuelbesliu Community Note • Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request. • Please do not leave "+1" or other comments that do not add relevant new information or questions. • If you are interested in working on this issue or have submitted a pull request, please leave a comment. --- Overview of the Issue On the Azure DevOps provider, the
    --ignore-vcs-status-names
    server flag is ignored when Atlantis computes whether a pull request is mergeable.
    VCSClient.PullIsMergeable
    is defined as
    PullIsMergeable(logger, repo, pull, vcsStatusName string, ignoreVCSStatusNames []string)
    and both the GitHub and GitLab clients honor those two parameters. The Azure DevOps client, however, discards them (
    _ string, _ []string
    ) and instead hardcodes a skip of the status genre
    Atlantis Bot/atlantis
    with name
    apply
    . Two consequences: 1.
    --ignore-vcs-status-names
    has no effect on Azure DevOps.
    Operators cannot exclude an additional blocking status-check branch policy (for example a human-only merge gate, or a second Atlantis service) from Atlantis' mergeability evaluation — a capability GitHub users already have via this documented flag. 2. The self-apply skip does not track
    --vcs-status-name
    .
    Atlantis posts its own statuses under the configured VCS status name (genre
    Atlantis Bot/<vcs-status-name>
    ), but the Azure DevOps skip is hardcoded to the literal
    Atlantis Bot/atlantis
    . With a non-default
    ATLANTIS_VCS_STATUS_NAME
    , the apply status genre no longer matches the hardcoded value, unlike the GitHub implementation which uses the configured name. Reproduction Steps 1. Run Atlantis against an Azure DevOps repo with
    apply_requirements: [mergeable]
    . 2. Add a blocking status-check branch policy that is not Atlantis' own status (for example a SonarQube/quality gate, or a proxy status used as a human merge gate). 3. Start the server with
    --ignore-vcs-status-names
    set to that policy's status genre or name. 4. Observe that Atlantis still treats the PR as not mergeable because the blocking policy is not approved — the flag is not honored on Azure DevOps. Environment details • Atlantis version: main (reproducible in current code) • VCS: Azure DevOps • Relevant flags:
    --ignore-vcs-status-names
    ,
    --vcs-status-name
    • Relevant code:
    server/events/vcs/azuredevops/client.go
    PullIsMergeable
    (params discarded; hardcoded `Atlantis Bot/atlantis`/`apply` skip), vs
    server/events/vcs/github/client.go
    IsMergeableMinusApply
    which honors both params. Additional Context • The flag is documented in
    <http://runatlantis.io/docs/server-configuration.md|runatlantis.io/docs/server-configuration.md>
    under
    --ignore-vcs-status-names
    , previously noted as "Currently only implemented for GitHub." • Proposed fix (provider parity, mirroring GitHub): honor
    vcsStatusName
    for the self-apply skip (
    Atlantis Bot/<vcs-status-name>
    +
    apply
    , keeping default
    atlantis
    backward compatible) and skip any branch policy whose status genre or name is listed in
    --ignore-vcs-status-names
    . • Pull request implementing this: #6921 runatlantis/atlantis
  • g

    GitHub

    09/24/2026, 9:35 PM
    #6924 bug: bare `atlantis apply` rejects the whole PR if any project errored or is already applied (since v0.46.0) Issue created by jonanderson10 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. --- Overview of the Issue After upgrading from v0.42.0 to v0.48.0, a bare
    atlantis apply
    fails without applying anything if any project on the PR doesn't have a successful plan. On v0.42.0 the successful plans were applied and the failed projects were skipped. We get one of these errors, depending on which project is in a bad state:
    Copy code
    plan for dir "services/standalone_elig" workspace "qa" project "standalone_elig" has status "plan_errored" and cannot be applied; run `atlantis plan`
    plan for dir "services/remittance" workspace "qa" project "remittance" errored and cannot be applied without a plan file; run `atlantis plan`
    plan for dir "services/operations_automation/change_of_address" workspace "qa" project "operations_automation_change_of_address" has status "applied" and cannot be applied; run `atlantis plan`
    The third error means that once you apply one project with
    -p
    , bare apply stays blocked on that PR until everything is re-planned. We don't use
    depends_on
    or
    abort_on_execution_order_fail
    . The check looks like
    validatePullStatusHasPlanFiles
    in
    server/events/project_command_builder.go
    , added in #6605 (v0.46.0). It returns at the first project with an errored or missing plan, which fails the whole command. Reproduction Steps 1. Use a repo with several projects and
    parallel_apply: true
    . 2. Open a PR where one project's plan fails (a Terraform error, or a lock held by another PR) and the rest plan successfully. 3. Comment
    atlantis apply
    . Nothing is applied and you get an error for the failed project. 4. Comment
    atlantis apply -p <good project>
    . It applies. 5. Comment
    atlantis apply
    again. Nothing is applied and you get
    has status "applied" and cannot be applied
    . Logs Redacted PR timeline (50 projects, 2 failed plans): https://gist.github.com/jonanderson10/ab6e0c33c27530f3d7a1da26fab21e5d Environment details • Atlantis version: v0.48.0 (`ghcr.io/runatlantis/atlantis:v0.48.0`), upgraded from v0.42.0 with no config changes • Deployment method: AWS ECS, GitHub • Relevant server flags:
    ATLANTIS_ENABLE_REGEXP_CMD=true
    ,
    ATLANTIS_PARALLEL_POOL_SIZE=3
    ,
    ATLANTIS_SILENCE_NO_PROJECTS=true
    .
    --disable-apply-all
    is not set. No
    apply_requirements
    . • Repo
    atlantis.yaml
    (trimmed, ~170 projects): version: 3 parallel_plan: true parallel_apply: true projects: - name: standalone_elig dir: ./services/standalone_elig workspace: <workspace> # replaced with the base branch by a pre_workflow_hook execution_order_group: 699 - name: remittance dir: ./services/remittance workspace: <workspace> execution_order_group: 699 Additional Context • Similar feature requests: #2325, #3042. v0.42.0 already behaved this way for us. • Possibly related: #6790, #6848, #6849 (0.48 plan verification changes). • Not duplicates: #6641, #6843, #6642. • Request: have bare apply skip projects without a valid plan (and projects already applied), report them, and apply the rest. If the new default is intentional, an opt-in (e.g.
    continue_on_plan_failure
    ) would work for us. runatlantis/atlantis
  • g

    GitHub

    09/25/2026, 2:42 PM
    #6925 Expose optional Terraform JSON plan in drift detection responses Issue created by melslow 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 a consumer of
    POST /api/drift/detect
    , I can request human-readable
    plan_output
    following #6709. I’d also like Terraform’s machine-readable plan so I can inspect drift programmatically without parsing text output. Describe the solution you'd like Add a default-off
    include_plan_json
    request field, independent of
    include_plan_output
    . When requested, return the full
    terraform show -json
    result as a JSON object in each project’s
    plan_json
    field. Generate it while the project’s plan execution lock is held. Return this data only in the immediate detect response, not in stored drift status or webhooks. If a project’s text plan succeeds but JSON cannot be produced, preserve its drift result, omit
    plan_json
    , and provide a per-project
    plan_json_error
    explaining why. Describe the drawbacks of your solution Generating JSON adds a command and can substantially increase response size. Full Terraform plan JSON may contain sensitive values, so it should require an explicit opt-in and be documented accordingly. JSON may be unavailable even after a successful text plan—for example, when remote operations or a custom workflow do not produce a local plan file. Supporting it also adds API fields and per-project error handling to maintain. Describe alternatives you've considered • Parse `plan_output`: Avoids another command, but text output is intended for humans and is less reliable for programmatic inspection. • Read JSON only when a workflow already produces it: Avoids running
    show
    , but the default plan workflow does not produce a JSON file. Users would need to change their workflows to benefit. • Run
    show -json
    after plan execution in the API controller:
    Keeps plan execution unchanged, but separates JSON generation from the project’s execution lock and environment. runatlantis/atlantis
  • g

    GitHub

    09/28/2026, 7:45 PM
    #6930 silence_pr_comments hides short failure messages such as project lock conflicts Issue created by mubarak-j 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 We run Atlantis for many repositories and projects. Long plan comments on GitHub pull requests caused GitHub to rate limit us, so we set
    silence_pr_comments: ["plan"]
    in the server-side repo configuration. This option fixes the rate limit problem. But the option also hides the comment that tells a user why a plan did not run. If another pull request holds the project lock, Atlantis does not post this message:
    Plan Failed: This project is currently locked by an unapplied plan from pull
    #<PR-number>
    . To continue, delete the lock from
    #<PR-number>
    or apply that plan and merge the pull request.
    Image The user sees only a failed
    atlantis/plan
    commit status and no reason is stated in the failed plan link, as shown below: Image The user must then search the Atlantis server logs to find the cause, which leds to wasted time and user frustration. Other short failure messages have the same problem. Steps to reproduce: 1. Set
    silence_pr_comments: ["plan"]
    for a repository. 2. Open pull request A that changes project
    foo
    . Atlantis plans it and locks
    foo
    . 3. Open pull request B that also changes project
    foo
    . 4. Atlantis sets a failed plan status on pull request B, but it posts no comment that names the lock or pull request A. Describe the solution you'd like If a project result has a failure message, Atlantis posts it even when the command is in
    silence_pr_comments
    . Atlantis continues to silence plan output, apply output, and errors, because they contain Terraform output and can be long. Failure messages are short, fixed text that Atlantis writes. They include a project lock held by another pull request, unmet plan or apply requirements, and unapplied project dependencies. They tell the user what to do next. The change is one condition in
    PullUpdater.updatePull
    in
    server/events/pull_updater.go
    , a unit test, and one sentence in the
    silence_pr_comments
    docs. An implementation is ready on this branch. When maintainers accept this issue, I can open a pull request. Describe the drawbacks of your solution Repositories that use
    silence_pr_comments
    get some comments again. Each comment is short, and Atlantis puts all failed projects of one command into one comment. The rate limit risk is much lower than for plan output, but it is not zero. For example, a pull request with many locked projects gets one comment on each autoplan. Users who want no comments at all for a stage lose that behavior for failures. Describe alternatives you've considered 1. Post only the project lock message. This needs a new marker on the project result for lock failures, set at each lock call site in
    server/events/project_command_runner.go
    . Unmet requirements cause the same confusion, so the wider rule seems more useful. 2. Add a new configuration value that keeps failure comments while the rest of the stage is silenced. This keeps the current default, but it adds one more option to the configuration and the docs. 3. Rely on the commit status. The status description is short, and Atlantis uses it for project counts. It has no space for the lock reason and the link to the other pull request. runatlantis/atlantis
  • g

    GitHub

    09/29/2026, 1:42 PM
    #6933 GitLab: combined `atlantis/apply` status stays `running` forever when an MR is merged with unapplied plans Issue created by bronto-rikstv 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. --- Overview of the Issue While a merge request is open, Atlantis keeps the combined
    atlantis/apply
    commit status at
    running
    as long as planned projects remain unapplied. That is correct and useful: it stops the MR from merging with unapplied changes. The problem is that this state can outlive the MR. If the MR is merged anyway (apply requirements permitting, or after a partial apply of only some projects), or closed without applying, nothing ever finalizes the combined status. On GitLab the
    external
    pipeline that carries it then shows
    running
    on the merged commit forever. The state has lost its purpose (there is no merge left to block), it suggests activity that is not happening, and it accumulates: these pipelines never reach a terminal state on their own, and GitLab's stuck-pipeline handling does not touch jobless external pipelines. Live example from our instance: an MR with two planned projects was merged after applying only one of them. Eight days later the merged commit's pipeline still shows
    atlantis/apply
    =
    running
    , and it will stay that way indefinitely. Reproduction Steps 1. GitLab project, MR with two or more Atlantis projects. 2.
    atlantis plan
    all of them;
    atlantis apply
    only one. 3. Merge the MR (or close it). 4. The combined
    atlantis/apply
    status, and with it the external pipeline on the head commit, stays
    running
    forever. Logs Not applicable: nothing fails, so nothing is logged. The behaviour is the absence of a final status write after the MR is closed or merged. Environment details • Atlantis v0.47.1 (`ghcr.io/runatlantis/atlantis:v0.47.1-debian`), ECS Fargate • GitLab self-managed 19.4.1 • Terragrunt custom workflow, config generated by terragrunt-atlantis-config Additional Context • Atlantis already reacts to MR close/merge events (locks and working directories are cleaned up), so there is a natural place to finalize the status. • Suggested behaviour: when an MR is merged or closed with the combined
    atlantis/apply
    (or
    atlantis/plan
    ) status still pending/running, write a terminal state. GitLab's commit status API accepts
    canceled
    (verified on 19.4.1), which describes the situation precisely: the work was abandoned, it neither succeeded nor failed. A description like "merge request closed with unapplied plans" would make it self-explanatory. • The wrinkle: Atlantis's internal status model only knows pending/success/failed, and not every VCS has a
    canceled
    commit state (GitHub does not), so this would be a per-VCS mapping decision. We wanted the maintainers' view on the direction before attempting any code. If the direction is agreed, we are happy to test a patch against our live GitLab 19.4.1, as we did for #6932. • Related observation, noted here for completeness: the
    /api/plan
    and
    /api/apply
    endpoints set the combined statuses to pending and never write a terminal state afterwards (
    server/controllers/api_controller.go
    , `apiPlan`/`apiApply`), so API-triggered runs against an MR strand `atlantis/plan`/`atlantis/apply` at
    running
    from the start. We noticed it while investigating, but we do not use these endpoints and do not have the capacity to propose a fix for that part; mentioning it in case it deserves its own issue. • Background: we ran into this while chasing stranded
    running
    pipelines on our repos (#6871, and the diagnosis that led to closing #5368). runatlantis/atlantis
  • g

    GitHub

    09/29/2026, 1:47 PM
    #5368 Wrong logic in `atlantis/apply` state transition Issue created by brontolinux 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. --- Overview of the Issue We are running Atlantis 0.31.0 in fargate. Our VCS is Gitlab (on premise). We use terragrunt with terraform, and in Atlantis we use a custom workflow to enable run-all. The Atlantis configuration is generated automatically via terragrunt-atlantis-config. When we run
    atlantis apply
    via a comment in a PR, all
    apply
    pipelines are executed. Going all pipelines' states to "succeeded", the main pipeline,
    atlantis/apply
    will remain running. The logs of the container will show something like this: {"level":"info","ts":"2025-01-19T164725.659Z","caller":"events/instrumented_project_command_runner.go:88","msg":"apply success. output available at: https://mygitserver.example.com/rikstv/sre/rikstv.terraform.infra.atlantistesting/-/merge_requests/15","json":{"repo":"rikstv/sre/rikstv.terraform.infra.atlantistesting","pull":"15"}} {"level":"info","ts":"2025-01-19T164727.254Z","caller":"vcs/gitlab_client.go:408","msg":"Updating GitLab commit status for 'atlantis/apply' to 'running'","json":{"repo":"rikstv/sre/rikstv.terraform.infra.atlantistesting","pull":"15"}} {"level":"info","ts":"2025-01-19T164727.343Z","caller":"vcs/gitlab_client.go:433","msg":"Pipeline found for commit 682ac035b55d8193a729b02edef6f8e71c8944ab, setting pipeline ID to 202381","json":{"repo":"rikstv/sre/rikstv.terraform.infra.atlantistesting","pull":"15"}} {"level":"warn","ts":"2025-01-19T164727.438Z","caller":"events/apply_command_runner.go:223","msg":"unable to update commit status: POST https://mygitserver.example.com/api/v4/projects/rikstv/sre/rikstv.terraform.infra.atlantistesting/statuses/682ac035b55d8193a729b02edef6f8e71c8944ab: 400 {message: Cannot transition status via :run from :running (Reason(s): Status cannot transition via \"run\")}","json":{"repo":"rikstv/sre/rikstv.terraform.infra.atlantistesting","pull":"15"},"stacktrace":"github.com/runatlantis/atlantis/server/events.(*ApplyCommandRunner).updateCommitStatus\n\tgithub.com/runatlantis/atlantis/server/events/apply_command_runner.go:223\ngithub.com/runatlantis/atlantis/server/events.(*ApplyCommandRunner).Run\n\tgithub.com/runatlantis/atlantis/server/events/apply_command_runner.go:181\ngithub.com/runatlantis/atlantis/server/events.(*DefaultCommandRunner).RunCommentCommand\n\tgithub.com/runatlantis/atlantis/server/events/command_runner.go:383"} I did some digging in the Go code, tag
    release-0.31
    . The error happens in
    atlantis/server/events/apply_command_runner.go
    , lines 214-224, running
    applyCommandRunner.commitStatusUpdater.UpdateCombinedCount
    . Unless I am mistaken, that will call the method
    UpdateCombinedCount
    from
    atlantis/server/events/commit_status_updater.go
    , lines 69-83. In there, we have this definition: src := fmt.Sprintf("%s/%s", d.StatusName, cmdName.String()) The string
    src
    is passed on to some `CommitStatusUpdater`'s method
    Client.UpdateStatus
    , whose result is what will be sent back to
    UpdateCombinedCount
    . There is a number of `Client`s defined, one for each VCS supported by Atlantis. Since we are using Gitlab, I went into
    atlantis/server/events/vcs/gitlab_client.go
    . In line 411 we have: logger.Info("Updating GitLab commit status for '%s' to '%s'", src, gitlabState) Now, I was supposed to find a log line with severity info in the logs, and it's actually the second line in the log above, and that line puts all the pieces back together: {"level":"info","ts":"2025-01-19T164727.254Z","caller":"vcs/gitlab_client.go:408","msg":"Updating GitLab commit status for 'atlantis/apply' to 'running'","json":{"repo":"rikstv/sre/rikstv.terraform.infra.atlantistesting","pull":"15"}} So, basically, Atlantis is trying to transition a pipeline, whose current status is
    running
    , to
    running
    again, which is wrong and is rejected by Gitlab. In fact, the first line in the log I posted reports a success, and Atlantis should transition the
    atlantis/apply
    pipeline to
    success
    instead, not `running`: {"level":"info","ts":"2025-01-19T164725.659Z","caller":"events/instrumented_project_command_runner.go:88","msg":"apply success. output available at: https://mygitserver.example.com/rikstv/sre/rikstv.terraform.infra.atlantistesting/-/merge_requests/15","json":{"repo":"rikstv/sre/rikstv.terraform.infra.atlantistesting","pull":"15"}} So there is something in the Atlantis' logic that selects the destination state that is wrong (from running, it should go to success or failed, definitely not running). I don't know where that logic is and what should be changed, you guys know better. Reproduction Steps • as a simple test, have a terraform stack that just emits outputs, without creating any infrastructure • have a few terragrunt units, each one with its own state, that use the code mentioned above In our case, the stack would be located at the path
    apps/sre/__selftest__
    , the terraform code would be in the
    _stack
    subdirectory, the units would be in environment-related directories, e.g.
    dev
    ,
    prod
    ..., each one with a
    terragrunt.hcl
    file that refers to the
    _stack
    directory. E.g.: # terragrunt.hcl include "root" { path = find_in_parent_folders() } terraform { source = "..//_stack" } # _stack/main.tf output "foo" { value = "foo" } output "bar" { value = "bar" } You will find our custom workflow in the "Environment details" section. Keep in mind that the workflow is tightly bound to the repository's directory structure: if you use a different directory scheme than the one suggested here, you'll have to update the workflow, too. Logs {"level":"info","ts":"2025-01-19T164725.659Z","caller":"events/instrumented_project_command_runner.go:88","msg":"apply success. output available at: https://mygitserver.example.com/rikstv/sre/rikstv.terraform.infra.atlantistesting/-/merge_requests/15","json":{"repo":"rikstv/sre/rikstv.terraform.infra.atlantistesting","pull":"15"}} {"level":"info","ts":"2025-01-19T164727.254Z","caller":"vcs/gitlab_client.go:408","msg":"Updating GitLab commit status for 'atlantis/apply' to 'running'","json":{"repo":"rikstv/sre/rikstv.terraform.infra.atlantistesting","pull":"15"}} {"level":"info","ts":"2025-01-19T164727.343Z","caller":"vcs/gitlab_client.go:433","msg":"Pipeline found for commit 682ac035b55d8193a729b02edef6f8e71c8944ab, setting pipeline ID to 202381","json":{"repo":"rikstv/sre/rikstv.terraform.infra.atlantistesting","pull":"15"}} {"level":"warn","ts":"2025-01-19T164727.438Z","caller":"events/apply_command_runner.go:223","msg":"unable to update commit status: POST <https://mygitserver.example.com/api/v4/projects/rikstv/sre/rikstv.… runatlantis/atlantis
    • 1
    • 1
  • g

    GitHub

    09/29/2026, 3:47 PM
    #6934 API operations (/api/plan, /api/apply, drift endpoints) are not drained on graceful shutdown Issue created by math3vz 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. --- Overview of the Issue On SIGTERM/SIGINT, Atlantis waits for in-progress operations to finish before exiting ("Received interrupt. Waiting for in-progress operations to complete"). That wait only covers work that comes in through VCS webhooks. Operations started through the HTTP API are not tracked by the drainer, so they get killed during shutdown: •
    POST /api/plan
    •
    POST /api/apply
    •
    POST /api/drift/detect
    •
    POST /api/drift/remediate
    How it happens (code links pinned to
    main
    @ 668fc54): 1. `Server.Start` calls
    waitForDrain()
    , then `server.Shutdown` with a 5s timeout, then returns, and the process exits. 2. The only callers of
    Drainer.StartOp()
    are `RunAutoplanCommand` and `RunCommentCommand`. 3. `APIController.Plan`, `Apply`, `Remediate` and `DetectDrift` run the whole operation synchronously inside the HTTP handler, and
    APIController
    has no reference to the
    Drainer
    . So when the server gets SIGTERM while an API plan/apply is running, the drainer sees 0 in-progress operations (or only the webhook ones) and finishes right away.
    server.Shutdown
    then waits at most 5s for the open API request, and the process exits. The terraform/tofu process dies mid-run, even if the orchestrator (e.g. Kubernetes
    terminationGracePeriodSeconds
    ) would have allowed much more time. The effects: • Orphaned remote state locks (e.g. S3/DynamoDB) that have to be `force-unlock`ed by hand. •
    POST /api/apply
    and
    POST /api/drift/remediate
    (with apply) can be cut off partway through an apply. • The API caller gets a dropped connection instead of a clean error. This matters most for drift detection and remediation, which are designed to be driven through the API by scheduled jobs, and for any rolling restart or node drain that happens while those jobs run. Expected behavior: API operations are drained like webhook operations. In-flight API requests finish before the server exits, and new API requests during shutdown get a clear error (e.g.
    503 Service Unavailable
    ), the same way webhook commands get the "Atlantis is shutting down" comment. Proposed fix (happy to open a PR once this is accepted): add the
    Drainer
    to
    APIController
    (wired in
    server.go
    ). In
    Plan
    ,
    Apply
    ,
    Remediate
    and
    DetectDrift
    , call
    StartOp()
    before any setup (working dir, locks) and return
    503
    if it returns
    false
    , then
    defer OpDone()
    . Add tests for the 503 during shutdown and for
    ShutdownBlocking()
    waiting on an in-flight API request. Reproduction Steps 1. Run Atlantis with the API enabled (
    --api-secret
    set) and a repo whose plan takes a while (e.g. a
    null_resource
    with a
    local-exec
    sleep 120
    , or any slow real project). 2. Start a plan via the API: curl -sS -X POST https://<atlantis>/api/plan \ -H "X-Atlantis-Token: $ATLANTIS_API_SECRET" -H 'Content-Type: application/json' \ -d '{"Repository":"<owner>/<repo>","Ref":"main","Type":"Github","Paths":[{"Directory":"<dir>","Workspace":"default"}]}' 3. While the plan is running, send SIGTERM to the Atlantis server (e.g.
    kubectl delete pod
    with a long
    terminationGracePeriodSeconds
    , or
    kill -TERM <pid>
    ). 4. Observe: the server logs "All in-progress operations complete, shutting down" right away (or after webhook ops only), exits within ~5s, the curl request fails with a dropped connection, and the terraform process is killed. With a remote backend, the state lock is left behind. 5. For comparison, repeat with
    atlantis plan
    commented on a PR: the server waits for the plan to finish before exiting. Logs Not captured from a controlled repro yet; this was found while investigating orphaned state locks and confirmed by reading the code. Based on
    Start()
    /
    waitForDrain()
    , the expected log sequence with only an API plan in flight is:
    Copy code
    Received interrupt. Waiting for in-progress operations to complete
    All in-progress operations complete, shutting down
    with no "Waiting for in-progress operations to complete, current in-progress ops: N" lines, even though the API plan is still running. Environment details • Atlantis version: v0.48.0 (also present on
    main
    @ 668fc54) • Deployment method: Kubernetes StatefulSet (plain manifests), custom image based on the official Alpine image • If not running the latest Atlantis version have you tried to reproduce this issue on the latest version: v0.48.0 is the latest release; the code paths above are unchanged on
    main
    • Atlantis flags (relevant):
    --enable-drift-detection
    ,
    --enable-drift-remediation
    ,
    --api-secret
    Any other information you can provide about the environment/deployment: AWS, OpenTofu via Terragrunt custom workflows, S3 backend with state locking. Drift detection/remediation is triggered by a scheduled job through
    /api/drift/detect
    and
    /api/drift/remediate
    . Additional Context • Related: #5949 (best-effort graceful termination of terraform processes on shutdown). That issue is about signalling the terraform processes; this one is about API operations not being waited on at all. • Original clean-shutdown work: #953, #1052. Recent drainer change: #6850. • ADR 0002 (API Enhancement and Drift Detection) doesn't cover shutdown behavior for API-driven operations. runatlantis/atlantis
  • g

    GitHub

    10/02/2026, 4:44 PM
    #6939 PROJECT_NAME not populated when autoplan Issue created by jficz 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. --- Overview of the Issue PROJECT_NAME env var in pre/post hooks is empty when the project has been run as part of autoplan. Reproduction Steps Autoplan a project in a repo with pre/post hooks. Logs N/A Environment details • Atlantis version: 0.48.0 • Deployment method: static binary • Atlantis flags: (none relevant) Atlantis server-side config file: repos: - id: /gitlab/repo/path branch: /.*/ ... post_workflow_hooks: - run: '[ "$PROJECT_NAME" = "my-project" ] && echo "do something"' commands: plan Repo
    atlantis.yaml
    file: version: 3 projects: - dir: prod name: some-project autoplan: when_modified: - "./*.tf" - "./*.tfvars" workflow: some-workflow - dir: prod name: my-project autoplan: when_modified: - "./my-special-config/*.yaml" workflow: my-special-workflow Additional Context runatlantis/atlantis
  • g

    GitHub

    10/02/2026, 6:10 PM
    #6940 Deleted directories are still planned after a new commit Issue created by mikealbert 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. --- Overview of the Issue After upgrading from v0.42.0 to v0.48.0, we started seeing plan failures on PRs that delete a whole Terraform root directory. If a PR plans a directory and then a later commit on the same PR deletes that directory, Atlantis still picks it up as a project and tries to plan it. The plan fails with:
    Copy code
    Error: Backend initialization required, please run "terraform init"
    
    Reason: Unsetting the previously set backend "s3"
    Reproduction Steps 1. Open a PR that changes a root directory with a remote backend, e.g.
    apps/foo/
    with an
    s3
    backend. 2.
    atlantis plan
    so Atlantis inits that directory 3. Push a new commit that deletes
    apps/foo/
    4. Run
    atlantis plan
    again Expected:
    apps/foo/
    is skipped since it no longer exists Actual: Atlantis plans
    apps/foo/
    and fails with the backend error above. Running
    atlantis plan
    again gives the same result.
    atlantis unlock
    doesn't help either, since it only removes plan files. Workaround: close and reopen the PR appears to be a viable workaround, however the PR still shows a failed status check for the missing directories. Logs From the server logs when the delete commit was planned:
    Copy code
    repo was already cloned but branch is not at correct commit, updating to "<sha>"
    Before the upgrade we only ever saw this one on new commits:
    Copy code
    repo appears to have changed base branch, must reclone
    Plan output for the deleted directory:
    Copy code
    running '/home/atlantis/.atlantis/bin/terraform1.14.4 plan -input=false -refresh -out /home/atlantis/.atlantis/repos/<org>/<repo>/<pr>/default/apps/foo/default.tfplan' in '/home/atlantis/.atlantis/repos/<org>/<repo>/<pr>/default/apps/foo': exit status 1
    ╷
    │ Error: Backend initialization required, please run "terraform init"
    │
    │ Reason: Unsetting the previously set backend "s3"
    ╵
    Environment details • Atlantis version: v0.48.0 • Deployment method: ECS on Fargate, custom image built
    FROM <http://ghcr.io/runatlantis/atlantis:v0.48.0-alpine|ghcr.io/runatlantis/atlantis:v0.48.0-alpine>
    . No persistent volume, so the data dir resets on task restart. • Terraform version: 1.14.4 • Checkout strategy: default (
    branch
    ) • Atlantis flags (env vars): •
    ATLANTIS_DISABLE_AUTOPLAN=true
    •
    ATLANTIS_AUTOPLAN_FILE_LIST=**/*.tf,**/*.yml,**/*.pkr.hcl,...
    •
    ATLANTIS_ENABLE_POLICY_CHECKS=true
    •
    ATLANTIS_QUIET_POLICY_CHECKS=true
    •
    ATLANTIS_ALLOW_DRAFT_PRS=true
    •
    ATLANTIS_ALLOW_REPO_CONFIG=false
    •
    ATLANTIS_LOCKING_DB_TYPE=redis
    •
    TF_CLI_ARGS_init=-reconfigure
    Atlantis server-side config file: repos: - id: /.*/ apply_requirements: [approved] allowed_overrides: [] allow_custom_workflows: true policies: owners: teams: - <team> policy_sets: - name: deny_instance_destroy path: /home/atlantis/conftest_policies/deny_resource_destroy/ source: local Repo
    atlantis.yaml
    file: none. Additional Context runatlantis/atlantis
  • g

    GitHub

    10/02/2026, 10:19 PM
    #6941 GitHub client: a permanent 404 makes GetModifiedFiles retry forever and GetPullRequest return nil, nil Issue created by tkensiski 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. --- Overview of the Issue Two retry loops in the GitHub client do not stop correctly when GitHub returns a 404 on every attempt. Both retry a 404 up to five times, because GitHub can return a short-lived 404 just after somebody opens a PR (#1019, #2002). A 404 that does not go away has two different results: •
    GetModifiedFiles
    never returns.
    After the fifth 404, the inner loop ends without a
    return
    or a
    break listloop
    . The outer
    for {}
    starts the same page again, so the goroutine stays in the loop forever. Each loop sends about 5 requests every 26 seconds. •
    GetPullRequest
    returns
    nil, nil
    .
    The loop declares
    pull, resp, err :=
    , which shadows the
    pull
    and
    err
    of the function. After five 404s, the function returns no pull request and no error.
    ParseGithubPull
    then reads
    pull.Head
    , which panics.
    PullIsMergeable
    gets an empty state and reports
    PR is in state <unknown>
    , instead of the 404. A token that cannot see the repo gives such a 404. One case: Atlantis holds the installation of one org and gets a webhook from a second org that is on the allowlist. The PR in the second org gets no comment and no status. Each event from that org starts one more loop, and each loop uses the rate limit of the installation that works. Reproduction Steps 1. Install one GitHub App in org A and in org B. The app webhook then sends the events of both orgs to Atlantis. 2. Start Atlantis with that app, and with
    --gh-app-installation-id
    set to the installation in org A. 3. Put both orgs on
    --repo-allowlist
    . 4. Open a PR in org B, so that Atlantis gets the
    pull_request
    webhook. 5. Atlantis replies
    200 Processing...
    . Then
    GET /repos/<org B>/<repo>/pulls/<n>/files
    returns 404 again and again, and it does not stop. We ran these steps against a mock of the GitHub API. One webhook caused 1,378 requests to
    /pulls/1/files
    in 2.5 hours, all of them 404, until we stopped the container. On github.com, a token for the installation of one org gets a 404 for a repo in another org. We confirmed this with two installations of one app. Logs Logs, from the test against the mock
    Copy code
    [attempt 1] GET /repos/infra-test-org/atlantis-multiorg-poc/pulls/1/files returned: 404
    [attempt 2] GET /repos/infra-test-org/atlantis-multiorg-poc/pulls/1/files returned: 404
    [attempt 3] GET /repos/infra-test-org/atlantis-multiorg-poc/pulls/1/files returned: 404
    [attempt 4] GET /repos/infra-test-org/atlantis-multiorg-poc/pulls/1/files returned: 404
    [attempt 5] GET /repos/infra-test-org/atlantis-multiorg-poc/pulls/1/files returned: 404
    [attempt 1] GET /repos/infra-test-org/atlantis-multiorg-poc/pulls/1/files returned: 404
    ...
    Environment details • Atlantis version: v0.48.0. The code is the same on
    main
    . • Deployment method: Docker, against a mock of the GitHub API. • Atlantis flags:
    --gh-app-id
    ,
    --gh-app-key-file
    ,
    --gh-app-installation-id
    ,
    --repo-allowlist
    with two orgs,
    --write-git-creds
    . Additional Context • `GetModifiedFiles`:
    server/events/vcs/github/client.go
    , the retry loop under
    listloop:
    . • `GetPullRequest`:
    server/events/vcs/github/client.go
    , the retry loop that calls
    PullRequests.Get
    . • A fix with tests for both is on tkensiski/atlantis, branch `fix/github-modified-files-404-loop`. I will open a PR that links this issue. runatlantis/atlantis
  • g

    GitHub

    10/04/2026, 2:27 AM
    #6947 skip-clone-no-changes should classify autodiscovered repos before cloning Issue created by dominic-r Problem
    --skip-clone-no-changes
    cannot skip an autoplan clone when a repository has no
    atlantis.yaml
    , even when the pull request changes no files in the configured autoplan file list. The pre-clone check returns false as soon as the remote repo config is absent, then autodiscovery runs after a full clone. This makes unrelated pull requests vulnerable to unnecessary clone work and to the opened-event/closed-event cleanup race that deletes the workspace during the clone. Proposed behavior When the remote repo config is absent, use the configured
    autoplan-file-list
    to classify the modified paths before cloning: • skip the clone when no modified path matches; • keep the clone and normal autodiscovery when any path matches; • leave explicit
    atlantis.yaml
    project handling unchanged. This preserves autodiscovery and plan/apply behavior for Terraform, OpenTofu, variable, Terragrunt, and lock-file changes while avoiding clones for unrelated changes. runatlantis/atlantis
  • g

    GitHub

    10/04/2026, 11:44 AM
    #6949 Redis Cluster: lock listing and PR-close cleanup miss locks on other primaries Issue created by rajeevsatya 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. --- Overview of the Issue With Atlantis v0.48.0 using its documented Redis Cluster locking configuration,
    GET /api/locks
    omitted an existing project lock, and closing the pull request left that lock behind. Direct scans of every cluster primary confirmed both discrepancies. A stale lock can block a later plan for the same project. Reproduction Steps 1. Run Atlantis v0.48.0 with two replicas and a three-primary Redis-protocol-compatible cluster for locking. Set
    ATLANTIS_LOCKING_DB_TYPE=redis
    and `ATLANTIS_REDIS_CLUSTER_ADDRESSES`; do not set the single-node host/port options. 2. Open a pull request selecting three independent Atlantis projects. In our controlled reproduction, the PR changed comments only. All three plans succeeded with no infrastructure changes. 3. Confirm the three
    pr/*
    lock keys are distributed across at least two primaries. Scan each primary directly, following its
    SCAN
    cursor to completion. Confirm that each stored lock value identifies the PR. 4. Compare these keys with
    GET /api/locks
    . 5. Close the PR without applying or merging. Wait for Atlantis to finish processing the close event, then scan every primary for those exact keys again. | Check | Expected | Observed | | ------------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | After three successful plans | Three locks in both /api/locks and the cluster | Three keys across two primaries; /api/locks returned only two | | After PR close cleanup finished | All three locks removed | Atlantis removed all three saved plans and reported successful cleanup, but one lock owned by the closed PR remained on another primary | There were no plan or apply jobs running on either replica when the remaining lock was checked. The PR was closed unmerged and no apply was run. A separate earlier run also left stale locks after PRs closed; this comment-only reproduction isolates the behavior from infrastructure changes. Logs The close-event logs reported deletion of all three saved plans and successful lock cleanup. Raw logs are omitted because they contain private repository and storage identifiers. The before/after cluster observations were recorded with exact lock ownership. Narrowly redacted log excerpts can be supplied if maintainers need particular messages. Environment details •
    atlantis version
    from the running pod:
    atlantis v0.48.0 (commit: 5a3bd40) (build date: 2026-09-22T02:57:08.336Z)
    . The binary came unchanged from the official
    <http://ghcr.io/runatlantis/atlantis:v0.48.0|ghcr.io/runatlantis/atlantis:v0.48.0>
    image. Our surrounding runtime image adds infrastructure tools but does not rebuild Atlantis or change its locking code. v0.48.0 was the latest release when reproduced. • Deployment: Kubernetes/Helm, using a chart based on Atlantis Helm chart 6.15.0; two Atlantis replicas share one locking cluster. • Locking backend: Redis-protocol-compatible Valkey Cluster with three primaries and replicas. This was not independently repeated on a Redis-branded cluster. • Relevant Atlantis settings (addresses are placeholders; authentication comes from a Kubernetes Secret):
    Copy code
    ATLANTIS_LOCKING_DB_TYPE=redis
    ATLANTIS_REDIS_CLUSTER_ADDRESSES=node-0.example.invalid:6379,node-1.example.invalid:6379,node-2.example.invalid:6379
    ATLANTIS_ENABLE_EXTERNAL_STORES=true
    Neither
    ATLANTIS_REDIS_HOST
    nor
    ATLANTIS_REDIS_PORT
    was configured. • Relevant server-side repo config (private identifiers omitted): external_stores: plan_store: type: s3 repos: - id: <private-repository> repo_config_file: <private-repo-config-file>.yaml repo_locks: mode: on_plan • Relevant repo config structure:
    version: 3
    ,
    parallel_plan: true
    , and three explicit projects with distinct directories. The actual alternate repo-config filename, project names, paths, branch rules, and unrelated workflows are omitted. Additional Context • Atlantis documents the cluster-address setting. • In v0.48.0, `List` and `UnlockByPull` each use one
    r.client.Scan(...).Iterator()
    to discover locks. The migration path uses the same pattern. This is a source-level inference: go-redis `Scan` does not explicitly traverse every cluster primary, while `ForEachMaster` provides that traversal. A scan routed to one node could explain both observations. • Issue #6765 concerns delayed unlocking while PR cleanup is still running. Here the close handler had finished, yet an owned lock remained. Could maintainers confirm the intended cluster-wide scan behavior and add a regression case with lock keys on different primaries? Listing, PR-close unlock, and migration appear to need explicit traversal of all primaries. Cleanup should also avoid deleting a lock if its ownership has changed since discovery. runatlantis/atlantis
  • g

    GitHub

    10/05/2026, 1:50 PM
    #6951 Persist plans for custom Atlantis workflows using external plan stores Issue created by gems2000 Community Note This request is related to, but distinct from, #6779, which tracks documentation for external_stores. This request is about using configured plan persistence from custom workflows. Are you willing to implement this feature? • I would be willing to implement this feature. Describe the user story As an Atlantis operator with custom Terraform or Terragrunt commands, I want Atlantis to persist and restore plans produced by explicitly selected run steps, so custom workflows can use the configured plan store with the same safety checks as native plan and apply steps. Describe the solution you'd like Allow a full-form custom run step to explicitly opt into plan-store behavior with plan_store.mode: save or consume. Save only after the marked producer succeeds, and validate/remove the plan around the marked consumer. Reject invalid stages, duplicate markers, and combinations that conflict with native plan/apply steps. Leave existing workflows unchanged by default. PR #6907 implements this proposal: #6907. Please review whether the configuration shape and producer/consumer lifecycle fit Atlantis's workflow and plan-store conventions. Describe the drawbacks of your solution This adds configuration and validation rules to custom workflows. Users must mark the producer and consumer correctly, and Atlantis must keep the plan lifecycle consistent across Terraform and Terragrunt commands. The opt-in mode, validation, and regression tests are intended to keep existing behavior unchanged and avoid guessing which arbitrary command owns a plan. Describe alternatives you've considered • Use native plan and apply steps. This does not cover custom command sequences that need full-form run steps. • Persist plans in custom scripts outside Atlantis. This duplicates plan-store handling and validation in each repository. • Keep current behavior. Custom workflows would continue to bypass Atlantis's configured plan persistence. Additional context The PR includes tests for stale, empty, missing, mutated, and failed plans, plus configuration validation and documentation. The existing related issue #6779 covers documenting external_stores, rather than plan persistence in custom workflows. runatlantis/atlantis
  • g

    GitHub

    10/05/2026, 8:40 PM
    #6953 Define architecture decision governance and feature lifecycle policy Issue created by pseudomorph Atlantis needs a clear path from a published proposal to an accepted decision, and a shared way to document experimental features and compatibility commitments. PR #6923 proposes two independently reviewable ADRs: • Governance: publication as Proposed, separate acceptance or rejection, two maintainer approvals, and a 14-day final review with recorded objection dispositions. • Lifecycle: explicit experiment permissions, evidence-backed graduation checklists, release availability, and removal notice measured in releases and days. Feedback is requested on the decision rules, experimental permission versus activation, and compatibility guarantees. Lifecycle records remain in Markdown; runtime registry and documentation cross-checking are outside the proposal. Feature-specific migrations and tooling require separate implementation work. Drift detection is the initial experiment candidate. Gating the existing API as a whole is not proposed. Introducing mandatory permissions for existing installations requires an accepted migration decision and release-specific guidance. runatlantis/atlantis
  • g

    GitHub

    10/06/2026, 2:11 AM
    #6155 feat: Include 'to forget' count in plan summary for Terraform/OpenTofu 1.7+ removed blocks Issue created by markshields-tl 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 a developer using Terraform 1.7+ or OpenTofu 1.7+
    removed
    blocks with
    destroy = false
    , I want Atlantis to display the "to forget" count in the plan summary, but currently this information is not captured by the plan parsing regex. Describe the solution you'd like Update the
    rePlanChanges
    regex in
    server/events/models/models.go
    to optionally capture the "to forget" count, similar to how "to import" was added in #3621. Current regex: rePlanChanges = regexp.MustCompile(
    Plan: (?:(\d+) to import, )?(\d+) to add, (\d+) to change, (\d+) to destroy.
    ) Proposed regex (example): rePlanChanges = regexp.MustCompile(
    Plan: (?:(\d+) to import, )?(\d+) to add, (\d+) to change, (?:(\d+) to forget, )?(\d+) to destroy.
    ) This would allow Atlantis to properly parse and display plan output like:
    Copy code
    Plan: 3 to add, 0 to change, 2 to forget, 0 to destroy.
    Background Terraform 1.7 and OpenTofu 1.7 introduced the
    removed
    block, which allows declaratively removing resources from state without destroying them: removed { from = aws_instance.example lifecycle { destroy = false } } When
    destroy = false
    , the plan output includes "X to forget" to indicate resources being removed from state management. This is the declarative equivalent of
    terraform state rm
    . References: • Terraform removed block docs • OpenTofu removed block PR • PR #3621 - precedent for adding "to import" Describe the drawbacks of your solution • Minor increase in regex complexity • Need to update
    PlanSuccessStats
    struct and related code to track the new count • Should be backward compatible since "to forget" is optional in the regex Describe alternatives you've considered • Ignoring the "to forget" count: Not ideal because users lose visibility into state-only changes • Manual state management: Using
    terraform state rm
    outside of Atlantis workflow, but this defeats the purpose of declarative IaC runatlantis/atlantis
    • 1
    • 1
  • g

    GitHub

    10/06/2026, 9:21 PM
    #6955 `parallel_plan` is ignored for API plan and drift detection requests Issue created by lnutimura 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 who runs plans through the API (
    POST /api/plan
    ,
    POST /api/drift/detect
    ,
    POST /api/drift/remediate
    ), I want one request covering many projects to plan them in parallel, the same way
    atlantis plan
    does on a pull request when
    parallel_plan: true
    is set. Right now the API plans one project at a time. A drift detection request across a few dozen projects takes the sum of every plan's duration. Long requests also run into HTTP timeouts (see #6795). The cause is in
    server/controllers/api_controller.go
    .
    apiPlan
    loops over the plan commands and calls
    events.RunOneProjectCmd
    for each project in turn, and policy checks use the same plain loop.
    parallel_plan
    ,
    parallel_policy_check
    and
    --parallel-pool-size
    are only honored by the PR command path (
    runProjectCmdsParallel
    /
    runProjectCmdsWithCancellationTracker
    in
    server/events/project_command_pool_executor.go
    ). The project contexts the API builds already carry
    ParallelPlanEnabled
    and
    ParallelPolicyCheckEnabled
    , set the same way as for PR commands. The only missing pieces are the pool size and a parallel code path. The only current workaround is to split the projects across several concurrent API requests on the client. That works because each request gets its own synthetic pull number and working dir. But it pushes the scheduling onto every client, and each request repeats the clone and hook overhead. Describe the solution you'd like Have API plans and policy checks honor the existing settings. No new configuration: • Use a bounded pool when
    --parallel-pool-size > 1
    , the request has more than one project, and the projects have
    parallel_plan
    enabled (
    parallel_policy_check
    for policy checks). Otherwise keep the current sequential loop unchanged. • Run at most
    --parallel-pool-size
    projects of one request at once. • Respect execution order groups: a group starts only after the previous group has finished. • Return results in request order rather than completion order, so API responses and drift results stay deterministic. • Never stop early: every project gets a result, matching today's API behavior. • In parallel mode, run all pre-workflow hooks first, then the batch of plans, then all post-workflow hooks. Hooks share the working dir with the plans, so interleaving them with concurrent plans is unsafe. Sequential mode keeps the current per-project
    pre → plan → post
    order. • Covers
    /api/plan
    ,
    /api/drift/detect
    , and the plan step of
    /api/drift/remediate
    . • Keep applies sequential. The non-PR apply path runs
    ValidateNonPRAPIRefUnchanged
    , which does a
    git fetch
    in the shared clone. Running those fetches concurrently would race on git ref locks. Parallel API apply could be a follow-up that does the ref check once per request before the batch. Concurrent plans in one clone rely on the same mechanisms PR parallel plans already use: the per-project lock,
    GitReadLock
    held during
    runSteps
    , and the read-lock fast path in
    Clone()
    from #6376. The API resolves the requested ref to a commit SHA before building commands, so each project's
    Clone()
    takes that fast path. I have a working implementation, with tests run under
    -race
    . The main parts: • A new exported helper in
    project_command_pool_executor.go
    ,
    RunProjectCmdsParallelInOrder(cmds, runnerFunc, poolSize)
    , built on
    sizedwaitgroup
    . It waits for the pool to drain when the execution order group changes and writes results by index. • A
    ParallelPoolSize
    field on
    APIController
    , wired from
    userConfig.ParallelPoolSize
    in
    server.go
    . • A parallel branch in
    apiPlan
    for plans and for policy checks.
    apiApply
    is unchanged. • One-line doc notes for
    parallel_plan
    ,
    --parallel-pool-size
    , and the drift detection endpoint. Describe the drawbacks of your solution • Behavior change for existing users: anyone with
    parallel_plan: true
    gets parallel API plans after upgrading. That means more concurrent Terraform processes from one request, so more CPU and memory use and more pressure on provider rate limits. This matches what they already opted into for PRs, but it's new on the API path. If maintainers prefer, it could sit behind an opt-in flag, but I'd rather reuse the existing setting. • Hook ordering differs between modes: in parallel mode, a post-workflow hook for project A runs after every plan in the batch, not right after A's plan. Hooks that assume strict interleaving would see a different order. It's the same trade-off the PR path makes. • Shared caches: the known plugin and provider cache races under parallel plans (#2412, #5984) now apply to API requests too. This adds no new mechanism, just more exposure. • Conflicts with in-flight work: this touches the same
    apiPlan
    code as #5994 (hook order) and the open plan-generation PRs (#6657, #6855). Whichever lands later needs a rebase. Describe alternatives you've considered • Client-side concurrency (several smaller API requests in flight): works today, but every client has to reimplement scheduling, server-side
    --parallel-pool-size
    can't bound it, and clone and hook overhead repeat per request. • Async drift detection (#6795): solves the HTTP-timeout side of long runs but not the total runtime. The two are complementary. • Parallelizing applies too: left out of scope because of the
    git fetch
    race described above. runatlantis/atlantis
  • g

    GitHub

    10/06/2026, 11:49 PM
    #6956 Locks taken by API commands (negative pull numbers) are never released if the request dies Issue created by math3vz 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. --- Overview of the Issue API commands (
    /api/plan
    ,
    /api/apply
    ,
    /api/drift/detect
    ,
    /api/drift/remediate
    ) run under a synthetic negative pull number from `nextNonPRPullNum()`. Project locks taken during the plan belong to that number, and only a
    defer a.Locker.UnlockByPull(...)
    in the handler releases them. If that defer never runs, the locks stay forever: • The process is killed or restarted mid-request (see #6934: API operations are not drained on shutdown). • The defer is registered only after
    apiPlan
    returns without error (`DetectDrift`, `Plan`, `Apply`).
    apiPlan
    can return an error after some projects were already planned and locked, for example a failing pre-workflow hook with
    --fail-on-pre-workflow-hook-error
    on a later project. Unlike a PR lock, nothing ever releases a synthetic one. There is no PR to close or merge, and the number is never reused (it is time-based). Every later PR plan and API call on those projects fails with
    This project is currently locked by an unapplied plan from pull #-993524529
    , until someone deletes each lock in the UI. We currently have 19 such locks from three synthetic pulls. Two came from a pod eviction during a nightly drift run; one is from 2026-08-24 and its cause is unknown. Reproduction Steps 1. Start
    POST /api/drift/detect
    (or
    /api/plan
    ) for several projects. 2. Restart the Atlantis process while the request is in flight. 3. `GET /api/locks`: the projects from the in-flight request are still locked with a negative
    PullID
    , and stay locked after restart. Logs Not attached.
    GET /api/locks
    shows entries like
    {"PullID": -993524529, "PullURL": "", "ProjectRepoPath": "live/...", "Time": "2026-10-02T11:53:29Z"}
    . Environment details • Atlantis version: v0.48.0 (code above checked against
    main
    @ 4149b21) • Deployment method: Kubernetes StatefulSet, single replica, BoltDB on a persistent volume • Atlantis flags: drift detection and remediation enabled,
    --parallel-plan
    Additional Context Possible fixes (any of these would help): 1. Register the
    UnlockByPull
    defer right after
    apiSetup
    , before
    apiPlan
    , in every API handler. 2. On server startup, delete locks whose
    PullID
    is negative. No live request can own them after a restart. runatlantis/atlantis
  • g

    GitHub

    10/07/2026, 2:36 PM
    #6694 feat: pluggable storage backends Issue created by pseudomorph 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. --- Pluggable storage backends Context Atlantis stores several kinds of data, and each kind currently hardcodes its own backend selection: • Coordination data (project locks, the global apply/command lock, pull status) goes through the
    db.Database
    interface.
    --locking-db-type
    picks BoltDB or Redis for all of it at once, and the Redis connection is configured through dedicated
    --redis-*
    flags. • Plan artifacts go through the
    planstore.PlanStore
    interface.
    --enable-external-stores
    plus the
    external_stores.plan_store
    block in the server-side repo config picks local filesystem or S3. • Job output lives in process memory with no backend at all. Two problems fall out of this. Connection configuration is tied to a single use case: the Redis flags exist only to serve locking, so pointing a second function (say, plans) at the same Redis requires new flags. And each new function invents its own selection mechanism: locking uses a flag, plans use a yaml block plus an enable flag, and a future jobs store would add a third pattern. Decision Split the two concepts and give each a home: • A backend declares how to reach a storage service: connection parameters, credentials, endpoint. Backends are configured once per type (
    boltdb
    ,
    filesystem
    ,
    redis
    , `s3`;
    memory
    is a future candidate) and know nothing about what data they hold.
    boltdb
    and
    filesystem
    live under the server's
    --data-dir
    and work without configuration. One instance per type keeps the config flat; if separate clusters of one type are ever needed, a named-instance form can be added without breaking this schema. • A store names a function that persists data: an interface scoped to one use case, plus one driver per backend type it supports. Each store binds to a backend type in config through its own schema, since stores don't share the same options; both take only
    backend
    today. On s3, each store appends its own key segment (
    plans/
    ) under the backend's
    prefix
    , so stores sharing the backend cannot collide. Stores are
    coordination
    and
    plans
    today;
    jobs
    ,
    logs
    , and remote work executors are future candidates. Naming
    coordination
    covers everything behind today's `db.Database`: project locks, the global apply/command lock, and pull status. These share transactional key-value semantics and always move between backends together. Pull status is state rather than a lock, so it may split into its own store later; nothing in this design blocks that.
    plans
    covers plan artifacts — the
    .tfplan
    files a store must survive a server restart with. It replaces the
    external_stores.plan_store
    block from #6312, which is deleted rather than deprecated because it never reached a release (latest tag v0.46.0 predates it). Backend types always use the concrete technology name —
    boltdb
    ,
    filesystem
    ,
    redis
    ,
    s3
    , later
    mysql
    or
    memory
    — so the yaml states what actually holds the data; an earlier draft had a
    local
    umbrella type whose meaning varied per store, dropped for exactly that ambiguity. Configuration Backends and bindings live in the server-side repo config: backends: redis: host: redis.internal port: 6379 tls_enabled: true s3: bucket: atlantis-data region: us-east-1 prefix: some/team stores: coordination: backend: redis plans: backend: s3 | store | local | redis | s3 | | ------------ | ------------------------------- | ------- | --- | | coordination | yes (BoltDB file) | yes | no | | plans | yes (workdir files, no restore) | planned | yes | | jobs | yes (in memory) | planned | no | Package layout
    Copy code
    server/core/backends/          # connection layer, function-agnostic
        backends.go                # Kind, Backend handles, lazy Registry
        boltdb.go                  # bolt file open
        redis.go                   # Redis dial (TLS, cluster selection, ping)
        s3.go                      # S3 client construction
        resolve.go                 # legacy-flag synthesis (removed with the flags)
    server/core/coordination/      # store: locks, apply lock, pull status
        coordination.go            # LockStore/PullStatusStore/CommandLockStore + Store + NewStore(backend)
        locking.go                 # Locker client (was core/locking)
        apply_locking.go           # ApplyLocker client (was core/locking)
        pull_status_updater.go     # pull-status write policy (was events.DBUpdater)
        boltdb/                    # driver (was core/boltdb)
        redis/                     # driver (was core/redis)
    server/core/planstore/         # store: plan artifacts
        plan_store.go              # PlanStore interface
        factory.go                 # New(backend) factory
        s3_plan_store.go           # s3 driver
    This change absorbs the old
    server/core/db
    (interface, now
    coordination.Store
    ),
    server/core/boltdb
    ,
    server/core/redis
    , and
    server/core/locking
    packages into
    server/core/coordination
    . The
    locking
    package merged in rather than staying a consumer because its entire content (
    Locker
    client,
    NoOpLocker
    ,
    ApplyLocker
    ) wraps the coordination store; the lock-key parsing the drivers needed from it moved to
    models.ParseLockKey
    , which also removes the driver→service import. The convention for a new store is a package under
    server/core/<store>/
    owning its interface, its service API, a
    New*(backend)
    factory that switches on backend kind, and one driver file or subpackage per supported kind. The convention for a new backend type is a
    Kind
    constant, a config struct in `raw`/`valid`, and a client constructor in
    backends
    — no store code changes. Constructed backends are typed handles behind the small
    backends.Backend
    interface —
    BoltDBBackend{DB}
    ,
    FilesystemBackend{}
    ,
    RedisBackend{Client}
    ,
    S3Backend{Client, Bucket, Prefix}
    — mirroring the per-type config structs, so a handle cannot carry another type's fields. Store factories type-switch on the concrete handle and take the client, not the config: opening the bolt file, dialing redis (TLS, cluster selection, ping), and building the S3 client happen once in the registry, and store drivers only add their data layout on top (bucket creation and key migration for boltdb, key schema for redis). The registry constructs backends lazily on first
    Get
    , so a configured-but-unbound backend is never dialed. Related consolidation Landing with this change:
    coordination.Store
    splits into
    LockStore
    ,
    PullStatusStore
    , and
    CommandLockStore
    so consumers depend on the slice they touch;
    events.DBUpdater
    becomes
    coordination.PullStatusUpdater
    (pull-identity freshness helpers move to
    models
    , result-classification types `DirNotExistErr`/`ErrStaleCommandHead` to
    command
    ); and
    NoOpLocker
    is deleted — per-project
    repo_locking: false
    and
    --disable-repo-locking
    early-return inside
    DefaultProjectLocker.TryLock
    instead of swapping in a fake locker. That last change means the locks UI and unlock paths under
    --disable-repo-locking
    now query the real store: pre-existing locks become visible and deletable rather than silently ignored, and an unreachable redis surfaces as an error where the no-op never could fail. Legacy flags `--locking… runatlantis/atlantis
    • 1
    • 1
  • g

    GitHub

    10/14/2026, 4:53 PM
    #6979 Mergeable bypass (--gh-allow-mergeable-bypass-apply) ignores required conversation resolution Issue created by math3vz 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. --- Overview of the Issue With
    --gh-allow-mergeable-bypass-apply
    and the
    mergeable
    apply requirement, Atlantis applies PRs that GitHub will not merge because of required conversation resolution. The apply succeeds, then automerge fails:
    Copy code
    Automerging failed:
    
    merging pull request: PUT <https://api.github.com/repos/<owner>/<repo>/pulls/<n>/merge>: 405 Repository rule violations found
    
    A conversation must be resolved before this pull request can be merged.
    The infrastructure is changed, but the PR stays open, so the default branch no longer matches what was applied. When
    atlantis/apply
    is a required check, GitHub reports the PR as
    blocked
    before apply.
    PullIsMergeable
    then calls
    IsMergeableMinusApply
    (
    server/events/vcs/github/client.go
    ). That function checks: •
    reviewDecision
    • the
    requiredApprovingReviewCount
    from active ruleset
    pull_request
    rules • required status checks (branch protection and rulesets), excluding
    atlantis/apply
    • required workflows It never reads
    requiredReviewThreadResolution
    (ruleset
    pull_request
    rule) or
    requiresConversationResolution
    (branch protection), and it doesn't look at the PR's review threads. A PR that is blocked only by
    atlantis/apply
    and an unresolved conversation therefore passes the bypass as mergeable. Reproduction Steps 1. Use a repo whose default branch has an active ruleset with a
    pull_request
    rule that requires conversation resolution (or classic branch protection with "Require conversation resolution before merging"), plus
    atlantis/apply
    as a required status check. 2. Run Atlantis with
    --gh-allow-mergeable-bypass-apply
    and
    --automerge
    , and set
    apply_requirements: [mergeable]
    . 3. Open a PR, get the required approval, and leave one review comment thread unresolved. 4. Comment
    atlantis apply
    . Expected: the apply is rejected because the PR isn't mergeable (the same as an unmet approval or a failing required check). Actual: the apply runs and automerge fails with the 405 above. Logs Logs
    Copy code
    Automerging failed:
    
    merging pull request: PUT <https://api.github.com/repos/<owner>/<repo>/pulls/<n>/merge>: 405 Repository rule violations found
    
    A conversation must be resolved before this pull request can be merged.
    
     []
    Environment details • Atlantis version: v0.48.0. The relevant code in
    server/events/vcs/github/client.go
    is unchanged on
    main
    . • Deployment method: Kubernetes StatefulSet • Atlantis flags:
    --gh-allow-mergeable-bypass-apply
    ,
    --automerge
    , GitHub App auth Atlantis server-side config file: repos: - id: /.*/ apply_requirements: [mergeable] Additional Context Proposed fix (I have it implemented with tests and am happy to open a PR once this is accepted): • Add
    requiredReviewThreadResolution
    to the
    PullRequestParameters
    fragment and
    requiresConversationResolution
    to
    branchProtectionRule
    in the
    GetPullRequestMergeabilityInfo
    query. • Add
    reviewThreads(first: 100, after: $threadCursor) { pageInfo { endCursor hasNextPage } nodes { isResolved } }
    to the pull request and page through it in the existing rules/contexts pagination loop. If it stopped at the first page, an unresolved thread after the first 100 would still let the apply through. • In
    IsMergeableMinusApply
    , return not mergeable when resolution is required and any thread is unresolved. References: • GitHub docs: Require conversation resolution before merging • GitHub GraphQL: `PullRequestParameters.requiredReviewThreadResolution` runatlantis/atlantis