Skip to content

fix(cli): release terraform state lock when interrupting diff/deploy - #284

Merged
jsteinich merged 2 commits into
open-constructs:mainfrom
X-Guardian:fix/terraform-interrupt
Jul 1, 2026
Merged

jsteinich merged 2 commits into
open-constructs:mainfrom
X-Guardian:fix/terraform-interrupt

Conversation

@X-Guardian

Copy link
Copy Markdown
Contributor

Related issue

Fixes #283

Description

Interrupting cdktn diff or cdktn deploy (Ctrl-C) left terraform's state lock held, so the next run failed with Error acquiring the state lock.

The cause was that the CLI's abort handler tore terraform down instead of letting it exit on its own. Terraform shares the CLI's process group, so Ctrl-C already reaches it directly and starts its graceful shutdown (which releases the lock) — but the CLI killed it before that shutdown could finish. The fix is to stop tearing terraform down on abort and instead wait for it to exit:

  • diff/plan (@cdktn/commons/src/util.ts): exec() no longer forwards the abort to child_process.spawn's signal option, which killed the child with SIGTERM and rejected the promise immediately (synthetic AbortError) before terraform had exited — so terraform never ran its graceful shutdown and the lock leaked. It now settles on the child's own close, so the run resolves only once terraform has exited and released the lock.

  • deploy/destroy (@cdktn/cli-core/src/lib/models/deploy-machine.ts): the xstate machine previously went straight to the final stopped state on STOP, killing the pty and resolving the run before terraform had finished shutting down. It now passes through an intermediate stopping state that marks the run cancelled and waits for terraform's natural EXITED before reaching stopped. The pty stays alive throughout, so terraform exits on its own and the run only resolves afterwards.

Why no extra signal is needed: every abort trigger in the codebase (runCdktfProject, watch) originates from process.on(SIGINT/SIGTERM/SIGQUIT), so terraform — sharing the process group — always receives the signal directly. There is no abort path that requires the CLI to deliver the signal itself.

Verification: confirmed after the fix that a single Ctrl-C produces only Interrupt received... Gracefully shutting down... and terraform exits cleanly on its own. The full lock-release path (interrupt a locking operation, then re-run) still needs a final check against a backend that locks state.

Checklist

  • I have updated the PR title to match CDKTN's style guide
  • I have run the linter on my code locally
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation if applicable
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works if applicable
  • New and existing unit tests pass locally with my changes

@X-Guardian
X-Guardian marked this pull request as ready for review June 30, 2026 12:47
@X-Guardian
X-Guardian requested a review from a team as a code owner June 30, 2026 12:47
@sakul-learning

Copy link
Copy Markdown
Contributor

Thanks for taking this on. I ran the PR head through our local termless cdktn-e2e path, including the unmerged e2e test from open-constructs/cdktn-e2e#3, and I’m seeing the diff/http-lock case still reproduce.

Exact setup/checks I ran:

cd /data/repos/cdktn-e2e

# build/publish/install the cdk-terrain PR head into a local sandbox CLI
CDKTN_MONOREPO=/data/repos/hermes-pr-reviewer/worktrees/cdk-terrain/open-constructs__cdk-terrain/pr-284 pnpm provision cdktn-prhead

# static check for the e2e repo
pnpm typecheck

# run the relevant termless interrupt/lock tests against the provisioned PR-head CLI
CLI_ID=cdktn-prhead pnpm exec vitest run tests/diff-lock-release.test.ts tests/ctrl-c-teardown.test.ts

Observed results:

  • pnpm provision cdktn-prhead: passed; installed a sandbox CLI from this PR head (0.23.3+prhead).
  • pnpm typecheck: passed.
  • CLI_ID=cdktn-prhead pnpm exec vitest run tests/diff-lock-release.test.ts tests/ctrl-c-teardown.test.ts: failed, with 2 failing tests and 3 passing tests.

The important failure is the unmerged PR #3 e2e test:

FAIL tests/diff-lock-release.test.ts
Ctrl-C during diff waits for terraform plan to unlock the HTTP backend

That test uses a deterministic in-process Terraform HTTP backend, so it directly records the backend LOCK / UNLOCK requests. Against this PR head I saw the backend receive lock but never unlock, so it reproduces the exact cdktn diff / HTTP-state-lock regression this PR is intended to fix.

I also saw this existing interrupt e2e fail against the same provisioned PR-head CLI:

FAIL tests/ctrl-c-teardown.test.ts
Ctrl-C at the deploy approval prompt exits cleanly and restores the cursor

That one hung after Ctrl-C at the Approve/Dismiss/Stop prompt (waitExit(..., 30_000) returned null).

Could you share how you validated the fix locally, and whether you tried the cdktn-e2e PR #3 test? It’s also possible our e2e harness is modeling the scenario incorrectly, so if you think the deterministic HTTP backend test is wrong I’d like to understand that before treating this as a PR blocker.

@X-Guardian

Copy link
Copy Markdown
Contributor Author

I have tested this locally. This is not interrupting Terraform at the approve prompt, but during the plan or apply process.

@jsteinich

Copy link
Copy Markdown
Contributor

@X-Guardian I think this looks good, but curious what the behavior of a 2nd Ctrl-C press is. My expectation is that it would abort gracefully shutdown.

@X-Guardian

Copy link
Copy Markdown
Contributor Author

I tested this and both Ctrl-C's get passed through to Terraform which then responds with:

Two interrupts received. Exiting immediately. Note that data loss may have occurred.

@jsteinich
jsteinich merged commit de617db into open-constructs:main Jul 1, 2026
259 checks passed
@X-Guardian
X-Guardian deleted the fix/terraform-interrupt branch July 2, 2026 09:52
so0k pushed a commit that referenced this pull request Jul 3, 2026
🤖 Release PR — merge to cut a new release. Kept open and rebased
as commits land on `main`.
---


<details><summary>0.23.4</summary>

##
[0.23.4](v0.23.3...v0.23.4)
(2026-07-02)


### Features

* **cli:** validate Terraform CLI feature versions
([#237](#237))
([32d434c](32d434c))
* declared Terraform/OpenTofu target versions (validation foundation)
([#269](#269))
([0aa2343](0aa2343))
* **lib:** S3-backend - Add `useLockfile` field and update
`dynamodbTable` deprecated JSDoc
([#234](#234))
([32a408f](32a408f))
* **lib:** validate Fn usage against Terraform/OpenTofu versions
([#268](#268))
([cdf8115](cdf8115))


### Bug Fixes

* **cli:** don't mistake terraform error output for a missing-variable
prompt
([#267](#267))
([0ea946f](0ea946f))
* **cli:** release terraform state lock when interrupting diff/deploy
([#284](#284))
([de617db](de617db))
* **cli:** resolve prebuilt Java provider versions via repo1.maven.org
([#285](#285))
([d5af07d](d5af07d))
* **gha:** Adding CHANGELOG.md to prettier ignore list
([#258](#258))
([5160469](5160469))
* **gha:** pull jsii-terraform CI image from GHCR instead of Docker Hub
([#261](#261))
([f19d34b](f19d34b))
* **provider-generator:** copy only publishable files into the jsii
compile bundle
([#292](#292))
([562d4c7](562d4c7))
* **provider-generator:** match shorthand provider sources against
OpenTofu schema keys
([#293](#293))
([fda0103](fda0103))
* **release:** add Changelog title for correct release-please insertion
([#255](#255))
([004818a](004818a))
* **release:** drop devEngines.packageManager that broke npm publish
([ef508a6](ef508a6))
* **tools:** repair generate-function-bindings tool
([#273](#273))
([821e1cf](821e1cf))


### Miscellaneous Chores

* Add Gradle to Mise config
([#271](#271))
([1578270](1578270))
* add pnpm to mise
([#266](#266))
([48ab91f](48ab91f))
* add uv to mise
([#260](#260))
([163cf3d](163cf3d))
* **deps:** bump nrwl/nx-set-shas from
38457b511e60ee4e1da09255b658e480748a1af4 to
afb73a62d26e41464e9254689e1fd6122ee683c1
([#236](#236))
([b5ac018](b5ac018))
* **deps:** bump the github-actions-backward-compatible group across 1
directory with 7 updates
([#235](#235))
([de67d9f](de67d9f))
* **deps:** replace uuid dependency with node:crypto.randomUUID
([#291](#291))
([6d3657a](6d3657a))
* ESLint ignore test edge-provider-bindings
([#270](#270))
([ba407bb](ba407bb))
* Fix Verdaccio logging config
([#272](#272))
([aded64b](aded64b))
* **gha:** parallelize provider binding generation for documentation
examples
([#244](#244))
([0a9c955](0a9c955))
* Migrate package manager from yarn to pnpm
([#170](#170))
([d23da4e](d23da4e))
* migrate planning artifacts to cdktn-planning [skip ci]
([968c358](968c358))
* **release:** add release-please automation for versioning
([#243](#243))
([c759eec](c759eec))
* Remove dependency-pinner tool
([#250](#250))
([42c27d5](42c27d5))
* Remove unused dependencies
([#281](#281))
([c7de65e](c7de65e))
* **tests:** run verdaccio in-process
([#248](#248))
([c17677d](c17677d))
* Update JSII version constraint
([#245](#245))
([d89558f](d89558f))
* Update scheduled workflows to only run on open-constructs/cdk-terrain
([#256](#256))
([ba408cf](ba408cf))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: open-constructs-cdktn[bot] <291052431+open-constructs-cdktn[bot]@users.noreply.github.com>
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

I'm going to lock this pull request because it has been closed for 30 days. This helps our maintainers find and focus on the active issues. If you've found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Aug 2, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cli: interrupting diff/deploy leaves the terraform state lock held

3 participants