Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Infrastructure as Code By James Joyner IV · · 9 min read

Pulumi Error Guide: 'the stack is currently locked by 1 lock(s)' — Clear a Stuck Update

Quick answer

Fix Pulumi's 'the stack is currently locked' / another update in progress error: understand stack locks, prove the holder is dead, cancel a stale update, and stop CI runs from deadlocking your stack.

  • #iac
  • #infrastructure-as-code
  • #troubleshooting
  • #errors
Free toolkit

Fixing errors like this? Get 500 free DevOps AI prompts

500 copy-paste AI prompts for the stack you actually run — one PDF, free.

Overview

Pulumi takes a lock on a stack whenever it runs an operation that can mutate the deployment — up, destroy, refresh, import, or a state edit. The lock guarantees that two updates never write to the same stack state at once and corrupt it. When Pulumi finds the stack already locked, it refuses to start and prints the lock holder:

error: the stack is currently locked by 1 lock(s). Either wait for the other
process(es) to end or delete the lock file with `pulumi cancel`.
  lock: s3://my-pulumi-state/.pulumi/locks/organization/my-app/prod/<uuid>.json:
  created by runner@ci-agent-4 (pid 1183) at 2026-07-08T14:22:10Z

Pulumi Cloud backends surface the same condition as an HTTP conflict:

error: [409] Conflict: Another update is currently in progress.
To learn more about the potential causes and resolution, see
https://www.pulumi.com/docs/troubleshooting/#conflict

The lock is doing its job. The key question is whether the holder is live (another up is genuinely running — wait for it) or stale (a previous run crashed, timed out, or was killed and never released the lock — cancel it). Running pulumi cancel on a live update can corrupt the stack’s state, so the entire workflow is about proving the holder is dead before you break the lock.

Symptoms

  • pulumi up / pulumi preview fails instantly with the stack is currently locked or [409] Conflict: Another update is currently in progress.
  • The lock line names a host/runner, a pid, and a creation timestamp.
  • CI jobs deadlock: a previous job on the same stack never released its lock, so every following job fails.
  • The named holder (a CI runner, pod, or laptop) is no longer running.
pulumi up
error: the stack is currently locked by 1 lock(s).
  lock: ... created by runner@ci-agent-4 (pid 1183) at 2026-07-08T14:22:10Z

Common Root Causes

1. A previous update was killed and never released the lock

The most common cause. A CI job hit its timeout, a runner was reclaimed mid-up, or someone Ctrl-C’d twice. Pulumi never reached the lock-release step, so the lock file (self-managed backend) or server-side update (Pulumi Cloud) persists.

created by runner@ci-agent-4 (pid 1183) at 2026-07-08T14:22:10Z   # hours ago; runner gone

2. A genuinely concurrent update (the lock is live)

Two pipelines, or a teammate and CI, ran up on the same stack at once. This is the lock working correctly — one must wait, not cancel.

3. A pending operation left in state

If an update was interrupted while a resource operation was in flight, Pulumi records a pending operation. Even after the lock clears, the next up warns that resources are in an unknown state and may refuse to proceed until reconciled.

warning: resource plugin ... The following resource(s) had operations that
were interrupted and may be in an inconsistent state:
  urn:pulumi:prod::my-app::aws:s3/bucket:Bucket::assets  (creating)

4. Backend connectivity or permission problems

For a self-managed S3/GCS/Azure Blob backend, an unreadable or unwritable lock prefix makes acquiring or releasing the lock fail. A throttled or unreachable backend can look like a lock error when the real issue is IAM or connectivity.

5. A leftover lock from a crashed local run

On a local file backend, an interrupted up can leave a lock file under ~/.pulumi or the state path that no live process owns.

Diagnostic Workflow

Step 1: Read the full lock / conflict details

pulumi up 2>&1 | grep -iE 'lock|conflict|created by'

Note the holder (created by ...), the pid, and the timestamp. A lock created seconds ago by a running job is live; one created hours ago by a dead runner is stale.

Step 2: Prove the holder is actually dead

This is the critical safety step — confirm the holder is not running before you cancel:

# If the holder is a Kubernetes pod / CI runner:
kubectl get pods -A | grep ci-agent-4

# If it is a person's machine, ask them. Do not cancel a live update.

For Pulumi Cloud, open the stack’s Activity/updates view in the console and confirm no update is genuinely in progress.

Step 3: Inspect the lock at the backend (self-managed)

You can confirm a stale lock file exists directly:

aws s3 ls s3://my-pulumi-state/.pulumi/locks/organization/my-app/prod/
2026-07-08 14:22:11   412  <uuid>.json

A lock file whose holder you have confirmed is dead is safe to cancel.

Step 4: Cancel the stale lock

Only after confirming the holder is dead, cancel the update on the exact stack:

pulumi cancel --stack organization/my-app/prod
This will irreversibly cancel the currently running update for 'prod'!
Please confirm that this is what you'd like to do by typing the stack name: prod

The currently running update for 'prod' has been canceled!

pulumi cancel releases the lock and marks the in-progress update as canceled. Never script it with --yes unless you have already proven the lock is stale.

Step 5: Reconcile pending operations, then verify

After cancelling, a killed update may have left pending operations. Refresh to reconcile state with reality before the next up:

pulumi refresh --stack organization/my-app/prod
pulumi preview --stack organization/my-app/prod

If refresh/preview reports pending operations it cannot resolve, export the state, remove the stale pending op, and re-import:

pulumi stack export --stack organization/my-app/prod > state.json
# edit out the stale entry under "pending_operations", then:
pulumi stack import --stack organization/my-app/prod --file state.json

Example Root Cause Analysis

A nightly pulumi up pipeline on the prod stack began failing every run with the stack is currently locked by 1 lock(s), holder runner@ci-agent-4, created at 14:22 the previous afternoon.

The team first checked whether an update was genuinely in progress — the concurrent-run case — before touching anything:

kubectl get pods -A | grep ci-agent-4
(no output — the runner pod no longer exists)

The CI history showed yesterday’s ci-agent-4 job had been evicted mid-up when its node was drained. Pulumi never ran its lock-release step, so the lock file in S3 persisted and blocked every subsequent run.

With the holder confirmed dead, the team cancelled the stale update and reconciled state:

pulumi cancel --stack organization/my-app/prod
pulumi refresh --stack organization/my-app/prod
The currently running update for 'prod' has been canceled!
...
Refreshing (prod): resources: 42 unchanged

The refresh reported no pending operations left behind, and the next preview was clean, so the pipeline resumed. To prevent recurrence, the CI job was given a graceful-shutdown grace period so an evicted run releases its lock before the pod is killed.

Prevention Best Practices

  • Never kill -9 a pulumi up; interrupt once and let it release the lock cleanly. See our infrastructure-as-code guides.
  • Serialize updates per stack in CI (concurrency groups / job queues) so two runs never target the same stack at once.
  • Give CI runners a shutdown grace period so an evicted job can release its lock before the pod is killed.
  • For self-managed backends, confirm the runner’s IAM role can read and write the .pulumi/locks prefix; a permission gap masquerades as a lock error.
  • Enable versioning on the state bucket so you can restore if a killed update left corrupt state.
  • After any forced cancel, always pulumi refresh before the next up to clear pending operations.

Quick Command Reference

# See the lock / conflict details from the error
pulumi up 2>&1 | grep -iE 'lock|conflict|created by'

# Confirm the holder is not still running before cancelling
kubectl get pods -A | grep RUNNER_NAME

# Inspect the lock file directly (self-managed S3 backend)
aws s3 ls s3://BUCKET/.pulumi/locks/ORG/PROJECT/STACK/

# Cancel a stale update/lock on the exact stack
pulumi cancel --stack ORG/PROJECT/STACK

# Reconcile state after a forced cancel, then verify
pulumi refresh --stack ORG/PROJECT/STACK
pulumi preview --stack ORG/PROJECT/STACK

Conclusion

the stack is currently locked (self-managed) and [409] Conflict: Another update is currently in progress (Pulumi Cloud) both mean something already holds the lock on your stack. The usual root causes:

  1. A previous update was killed and never released the lock (most common — stale).
  2. A genuinely concurrent update holds it (live — wait, do not cancel).
  3. A pending operation left in state by an interrupted run.
  4. Backend connectivity or IAM problems on the lock prefix.
  5. A leftover lock from a crashed local run.

Read the holder and timestamp, prove the holder is dead, then pulumi cancel — and always pulumi refresh to clear pending operations before the next up. Prevent recurrence with per-stack serialization, graceful CI shutdowns, correct backend permissions, and state versioning so an interrupted update never strands your team.

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.