Skip to content

Provider plugin processes keep running after Terraform panics (os.Exit(11) in PanicHandler skips plugin.CleanupClients) #39399

Open

Description

@andrewesweet

Terraform Version

Terraform v1.16.6
on linux_amd64
+ provider registry.terraform.io/hashicorp/random v3.9.1

Also reproduced with v1.15.8, v1.15.9, and a build of main at 9ec49e0 (v1.18.0-dev).

Terraform Configuration Files

main.tf:

terraform {
  required_providers {
    random = {
      source = "hashicorp/random"
    }
  }
}

resource "random_password" "this" {
  count  = 1
  length = 16
}

tests/main.tftest.hcl:

mock_provider "random" {}

run "assertion_fails" {
  command = apply

  assert {
    condition     = length(random_password.this) == 0
    error_message = "expected no password"
  }
}

The failing assertion triggers the panic tracked in #39365. That panic is used here only because it is a deterministic way to make Terraform crash while a provider plugin is running.

Debug Output

A full TF_LOG=trace log is available on request. Panic excerpt:

panic: unexpected error marshalling value: value has marks, so it cannot be serialized as JSON
goroutine 86 [running]:
runtime/debug.Stack()
	runtime/debug/stack.go:26 +0x5e
github.com/hashicorp/terraform/internal/logging.PanicHandler()
	github.com/hashicorp/terraform/internal/logging/panic.go:84 +0x15c
...
github.com/hashicorp/terraform/internal/command/views/json.NewDiagnostic({0x4ab02a0, 0x35a38c928120}, 0x35a38ca54c30)
	github.com/hashicorp/terraform/internal/command/views/json/diagnostic.go:278 +0x18de
...
github.com/hashicorp/terraform/internal/moduletest/graph.(*NodeTestRun).Execute(0x35a38c4c2cf0, 0x35a38bfc1260)
	github.com/hashicorp/terraform/internal/moduletest/graph/node_test_run.go:147 +0x4b8
github.com/hashicorp/terraform/internal/moduletest/graph.Walk.func1({0x4445fa0, 0x35a38c4c2cf0})
	github.com/hashicorp/terraform/internal/moduletest/graph/test_graph_builder.go:130 +0x257

In the trace log, the random plugin is started (provider: plugin started: ... pid=...) and never logs plugin process exited.

Expected Behavior

When Terraform crashes, it still terminates the provider plugin processes it started, as it does on a normal exit. No terraform-provider-* process outlives the terraform process.

Actual Behavior

terraform test exits with status 11 after the crash banner. The terraform-provider-random process it started keeps running. It is reparented to the init or subreaper process and is still running two minutes later:

$ terraform test; echo "exit=$?"
...
exit=11
$ ps -o pid,ppid,stat,etime,rss,comm -p 3250962   # 10 s after exit
    PID    PPID STAT     ELAPSED   RSS COMMAND
3250962     594 Sl         00:10 21504 terraform-provi
$ ps -o pid,ppid,stat,etime,rss,comm -p 3250962   # 120 s after exit
    PID    PPID STAT     ELAPSED   RSS COMMAND
3250962     594 Sl         02:00 10496 terraform-provi

PID 594 is the session's subreaper. The process has to be killed by hand. With a large provider such as hashicorp/aws, each orphan holds roughly 375-380 MB RSS. Repeated crashes in CI or in scripted test loops accumulate these processes.

Steps to Reproduce

  1. Save the two files above.
  2. terraform init
  3. terraform test; echo "exit=$?" prints exit=11 after the crash banner.
  4. pgrep -a terraform-provider lists the random plugin that is still running.

No credentials are needed.

Additional Context

The plugin is orphaned only when a provider instance is still alive at the moment of the panic. In terraform test, that is the case when the test file declares a provider or mock_provider block:

Test file declares Panic Orphaned plugins
no provider block (per-run provider) yes 0
provider "random" {} yes 1
mock_provider "random" {} yes 1

TestProvidersTransformer (internal/moduletest/graph/transform_providers.go) starts file-level providers while it builds the graph. They are closed only by NodeProviderClose, after all runs. Per-run providers are closed before the run's result is rendered, so they are already gone when the panic happens.

The orphaning itself is not specific to terraform test:

  • Worker goroutines defer logging.PanicHandler(). That function ends with os.Exit(11) (internal/logging/panic.go:89).
  • os.Exit skips deferred calls, including defer plugin.CleanupClients() in realMain (main.go:248).
  • Managed plugins (internal/command/meta_providers.go:467) are therefore never killed.

The same defer logging.PanicHandler() pattern guards goroutines in internal/terraform/graph.go, internal/terraform/context.go, internal/backend/local/backend_plan.go, backend_apply.go and backend_refresh.go. A panic in any of them would orphan running plugins. Only the terraform test path is demonstrated here.

A local experiment that calls plugin.CleanupClients() in PanicHandler immediately before os.Exit(11) removed the orphans in every case tried. Those cases were the configuration above, the same with provider "random" {}, plan mode, and a module using hashicorp/aws under mock_provider. The exit status stayed 11 and the crash report was unchanged. Whether that is the right place for the cleanup is open for discussion.

References

Generative AI / LLM assisted development?

An LLM coding agent helped narrow down the reproduction and read the source. Every result above was run and checked on the listed versions.

Activity

  1. manenim commented on Oct 16, 2026

    @manenim

    Hi, I’d like to work on this if it’s still available.

    I’d start with a regression test that checks for provider processes left running after a panic. Then I’d work on the cleanup path so those processes are terminated before Terraform exits. I’d keep the existing crash report and exit status unchanged.

    Please assign this to me if it’s available.

  2. added
    newnew issue not yet triaged
    on Oct 16, 2026
  3. andrewesweet commented on Oct 17, 2026

    @andrewesweet
    Author

    @manenim, in case it helps your work on this, some notes from reproducing it locally. Happy for you to take it forward; nothing here is meant to compete with your PR.

    The orphan comes from os.Exit(11) in logging.PanicHandler, which skips the deferred plugin.CleanupClients() in realMain. Calling plugin.CleanupClients() in PanicHandler immediately before os.Exit(11) was enough in my testing: it is a no-op when no managed clients exist, and the crash output and exit status 11 are unchanged. With the reproduction from this issue, three runs each: unpatched left one orphaned terraform-provider-random process per run; patched left none, still exiting 11.

    On the regression test, the tricky part I hit is that there is no existing deterministic way to make the CLI panic after a provider starts other than the #39365 crash itself, which the fix for that issue (#39406) removes. The options I could see were (a) an e2e test built on the #39365 trigger, which stops panicking once that fix lands; (b) adding a deprecated attribute to a test provider's schema so the trigger survives in test form; (c) a test-only panic hook, which has no precedent here; or (d) no automated test, with before/after evidence. The maintainers may have a preference.

    Disclosure: this investigation was done with an AI coding agent and reviewed by me.

  4. manenim commented on Oct 17, 2026

    @manenim

    Thanks @andrewesweet, this is helpful. I appreciate you sharing the reproduction results and the testing options.

    I’d like to check whether Terraform’s existing test helpers can support a process-level regression test. It would start a managed test provider, trigger the panic handler, and verify that the provider exits. That would keep the test independent of the crash being fixed in #39406.

    Could a maintainer confirm whether this is a suitable direction before I start?

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugnewnew issue not yet triaged

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions