Hedronite · Cert Lesson · HashiCorp / Terraform Pro · Tue 2026-08-18 · Trio #93

Terraform Pro Remote Operations — the lock you did not hold

The ID on stderr is a name. It is not a handle you earned.

Lesson Class: Cert (Terraform Pro · remote operations · force-unlock)
Sprint: TF track · day 27 · second Pro-depth fire · trio #93
Cloud Referent: HCP Terraform / Terraform Cloud remote run as the lock holder
Paired Ops: S3 + DynamoDB LockID, the same claim at AWS altitude
Paired Dev: subprocess wrapper that prints the ID and returns 2
Grounding: Brikman Ch.10 pp.596-599 first-use · Ch.3 pp.147-157 referenced · Lab 25 adjacent · no force-unlock lab
The run
HCP acquires. A laptop apply against that workspace is a stranger.
The burial
force-unlock only when the owner is dead. Brikman: leftover lock, CI crash.
The skip
-lock=false is two writers. Cancel is not unlock. Change sets are the wrong exam.
Remote operations move apply off the laptop. Force-unlock buries a claim whose owner will not return. Used on a living run it is theft.

<!-- hal:authoritative:yaml -->

The ID on stderr is a name. It is not a handle you earned.

§I — Frame

Associate-003 is closed. The first Pro-depth fire, three days ago, named two plan-time results that never appear in a .tf: a speculative plan that must not apply, and a run-task verdict HCP transports and does not compute. This fire is the lock those runs sit on.

A workspace on HCP Terraform (Terraform Cloud in the 2022 text) is a remote operation. The plan runs on HashiCorp's agents, or on your own agent, against state the workspace owns. While that run is in progress, the workspace holds a lock. A laptop in the same directory, pointed at the same backend, that starts terraform apply is a second writer. The second writer did not hold the lock.

The exam stem prints the error the Ops lesson already showed:

Error: Error acquiring the state lock
Lock Info:
  ID:        9b2a1c4e-7d11-4f0a-a6e3-0f3c8d21ab77
  Operation: OperationTypeApply
  Who:       hcp-run@ws-network-prod

Two answers look operational and are wrong. -lock=false writes anyway. terraform force-unlock 9b2a1c4e-... deletes the item while the run still believes it holds the claim. Brikman, in the team chapter, names the only case the second command is for: a lock whose owner is dead (Ch. 10, pp. 596-599). A live HCP run is not dead.

Coin the same name Ops and Dev coined: the lock you did not hold.

This lesson does not reopen speculative plans or run tasks. It does not reopen Sentinel. It does not inventory workspaces, run triggers, or state versions (07-28). CloudFormation change sets are yesterday's Python-day Cert. They are not a Terraform lock.

§II — Remote operations hold the claim

Fact one. The runner is the locker.

Brikman's deployment-server rule is blunt. Infrastructure changes should be applied from a CI server or from Terraform Cloud / Terraform Enterprise, not from a developer's computer (Ch. 10, pp. 597-599). The reason is the same reason today's Ops lesson put the file in S3: one writer, one claim, one environment that is not a laptop that can sleep mid-apply.

HCP Terraform is that server with a product name. A VCS-driven run, an API-driven run, and a UI-driven run all execute plan and (if allowed) apply against the workspace's state. The lock is acquired for the run. It is released when the run finishes or fails in a way that still runs the unlock. The laptop CLI, even with a valid token, is another client of the same backend.

Lab 25 drills the operational surface around that fact: VCS-driven lifecycle, API-driven runs, plan-only versus apply permissions. It does not name the lock. The Pro candidate still has to know that a plan-only permission can produce a plan and cannot apply, and that an apply permission on a workspace that already has a run in progress is not a second apply. It is a lock error.

Fact two. Local CLI against a remote-operations workspace is a stranger.

Teams bind a working directory to an HCP workspace with a cloud block or a remote backend. After init, terraform apply on the laptop does not apply on the laptop. It starts (or tries to start) a remote run. If a run is already in progress, the CLI prints the lock. The ID belongs to the run. Who names the runner, not you.

The Associate reflex is "I have the working directory, so I hold the state." Pro fails that reflex. Ownership is the lock item, not the checkout. The 07-28 lesson already taught that state versions live on the workspace. Today the only new claim is that the lock lives there too, for the duration of the run, and that a second client is the stranger the Ops lesson named as operator B.

A speculative plan (08-15) also acquires a lock long enough to plan. It still cannot apply. Force-unlocking a speculative plan so a laptop can apply is two mistakes stacked: you stole a review lock, and you applied from the place Brikman said not to apply from.

Fact three. The backend block still does not evaluate variables.

Lab 12 restates the Ch. 3 limitation the Ops lesson grounded (pp. 158-161): backend configuration is not ordinary HCL. No var, no local, no resource attribute. Partial configuration at init is the portable pattern (Lab 31). The Cert twist is remote operations. The workspace's backend (or the cloud block) is configured on the workspace, or at init, not interpolated at plan time. A candidate who "fixes" a locked apply by rewriting key from a variable has left the lock question and failed the backend-rule question.

terraform_remote_state remains read-only (Ch. 3, pp. 179-188). A consumer workspace does not acquire the producer's lock to read. If the producer is mid-apply, the consumer sees the last written state. That is why a run trigger (Lab 25 topology) fires after apply, not after plan. Reading mid-apply is stale. Writing mid-apply is the lock you did not hold.

§III — Force-unlock is a last resort for a dead owner

Brikman puts force-unlock in a section titled Errors releasing locks (Ch. 10, p. 597). The example is a CI server that crashes in the middle of terraform apply. The state remains permanently locked. Anyone else who tries to apply on the same module gets the locked error and the ID. The command is:

terraform force-unlock <LOCK_ID>

The precondition, in the book's own words, is that you are absolutely sure this is an accidentally leftover lock.

Translate that for Pro.

The owner is dead when the process that acquired the lock will not continue and will not release: a runner VM terminated, an HCP run stuck in a state the UI marks as discarded or cancelled with the lock left behind (rare, and you confirm in the run list first), a laptop that kernel-panicked, a job whose Who line names a host that no longer has a terraform process.

The owner is not dead when a run is listed as planning or applying, a colleague's terminal still shows Acquiring state lock or resource creation, a GitHub Actions job is still yellow, or you simply have not looked. Waiting, or -lock-timeout=10m (Ch. 3, p. 150), is the move. Force-unlock of a live owner is the lock you did not hold, spent as a command.

After you unlock a dead lock, you still have the apply that died. Brikman pairs this with Terraform state errors on the previous page (p. 596): if apply failed to save state (lost connectivity), Terraform writes errored.tfstate on disk. CI must not delete that file as workspace cleanup. terraform state push errored.tfstate restores it. Force-unlock without the push, when the crash also lost the write, unlocks a file that is missing the last resources. The next plan looks like drift. It is a lost write. That pair (stale lock, stale file) is one exam class. Do not treat them as the same command.

HCP remote operations complicate the disk file. The apply happened on an agent you may not SSH to. There is no errored.tfstate on your laptop. The recovery surface is the run's state version and the UI, not a local push. Confirm the run is terminal. Confirm no agent still holds the operation. Then unlock. Then inspect state. Then plan. Do not apply blind.

§III.A — Worked example: the ID that belonged to the run

Workspace ws-network-prod is VCS-backed. A merge to main starts run #184. HCP prints Acquiring state lock. The apply is creating a VPC peering that waits on an accepter. Elapsed time is four minutes.

Engineer C, on a laptop, in the clone, runs terraform apply because a tag cannot wait. The CLI talks to the same workspace. DynamoDB (or HCP's own lock, if the workspace is HCP-native state) refuses the conditional write. C sees the error block. Who is hcp-run@ws-network-prod. Operation is OperationTypeApply. ID is a UUID C did not mint.

C has three buttons in their head.

Wait. Correct. #184 will release. C's next apply is a new run, or a new local remote-run request, that acquires a new ID.

Cancel #184 from the UI, then wait for terminal, then apply. Correct if the peering must not finish. Cancel asks the runner to stop. It is not force-unlock.

terraform force-unlock with the printed UUID. Wrong. #184 is alive. Deleting the item lets C start a second apply against a file the first apply still writes.

The exam will change one fact. The Actions job is red. The host in Who has no process. The run list is empty. That is Brikman's leftover lock. Then, and only then, the UUID is a burial permit.

§IV — Failure mode: the lock you did not hold

Force-unlock as a reflex. The error printed an ID, so it felt like a handle. The ID is the other process's name. Using it is impersonation unless that process is gone. The Dev lesson's wrapper prints the ID and returns 2. It does not unlock. The exam answer that unlocks from a Python finally or from a laptop because "the pipeline looked stuck" is the wrong altitude of the same reflex.

**-lock=false as a convenience.** The flag exists. Against a shared backend, or against an HCP workspace, it is a second writer. Pro will offer it as the "unblock Friday" option. Friday's unlocked apply and the still-running remote run write the same object. Monday's plan is a ghost.

Laptop apply "just this once." Brikman's deployment-server pages exist because a laptop apply is a lock you will not finish if the lid closes (Ch. 10, pp. 597-599). HCP exists so the apply has a home that is not a sleep cycle. A workspace with remote operations enabled, plus a local apply with a local backend override, is two homes. Pick one.

Unlocking a speculative plan to apply it. 08-15 already forbade applying a speculative plan. Force-unlock does not change that. Merge, then a new run. Auto-apply does not make a PR plan apply.

Confusing force-unlock with cancel. Cancelling an HCP run asks the runner to stop and (if it still can) release. Force-unlock deletes the lock item whether the runner is alive or not. Cancel first when the run is visible. Unlock only when cancel finished and the lock remained, or when there is no run left to cancel.

Change sets. Yesterday's DOP lesson used CloudFormation change sets as a preview that is not an apply. A Terraform plan is not a change set. A speculative plan is not a change set. Force-unlock is not a change-set execute. If the stem says change set, you are on the wrong exam.

§V — Pairing

Ops is the DynamoDB item and the S3 key. Cert is the same item when the holder is an HCP run rather than operator A on a runner. Dev is the Python parent that must not unlock a lock the child holds.

08-15 remains the speculative-plan and run-task lesson. A remote operation can be speculative. Today's question is who holds the lock during it, not what the run task returned. 07-28 remains the workspace and state-version lesson. 08-09 remains Sentinel and secrets in state.

Lab 25 is adjacent, not a force-unlock lab. Lab 12 is adjacent backend rules. The tfpro set has no force-unlock lab. Empty-by-scope. The command is taught from Brikman Ch. 10 p. 597.

§VI — Drills

Question 1
An HCP workspace shows a run in Applying. A laptop in the linked directory runs terraform apply and prints Error acquiring the state lock plus a LockID. What is the correct next action?
tap to reveal
Wait for the run to finish, or cancel the run from the UI if the apply must stop. Do not force-unlock a live run. Do not pass -lock=false.
Question 2
A GitHub Actions runner is terminated mid-apply. The workspace stays locked. The Actions UI shows the job as cancelled. No terraform process remains. Brikman calls this an accidentally leftover lock (Ch. 10, p. 597). What command releases it, and what do you do before you type it?
tap to reveal
Confirm the owner is gone (no live run, no process on the host in Who). Then terraform force-unlock <LOCK_ID> with the ID from the error. Then inspect state. Then plan. If errored.tfstate exists on a disk you still have, consider state push before the next apply.
Question 3
Why is terraform apply -lock=false the wrong answer when a remote run holds the lock?
tap to reveal
The flag skips the claim. The remote run still writes. You write too. The last write wins. That is two writers, one file: the lock you did not hold.
Question 4
Lab 25 asks you to choose plan-only versus apply permission boundaries. A user with plan-only permission starts a run while an apply is in progress. What happens, and does plan-only let them force-unlock?
tap to reveal
The new run fails to acquire the lock (or queues, depending on product generation; it does not apply). Plan-only is not an unlock grant. Force-unlock is an administrative act for a dead lock, not a permission that turns a planner into a second applier.
Question 5
A speculative plan on a pull request holds the workspace lock long enough to plan. An engineer force-unlocks so they can apply the PR from a laptop. Name the two rules that breaks.
tap to reveal
A speculative plan must not apply (08-15). Applies belong on the deployment server / HCP run after merge, not on a laptop (Ch. 10, pp. 597-599). Force-unlock did not earn the lock. It stole a review lock.
Question 6
The tfpro labs never name force-unlock. Where does the Pro candidate ground the command, and what is the one precondition the book attaches?
tap to reveal
Brikman Ch. 10, p. 597, Errors releasing locks. Precondition: you are absolutely sure the lock is leftover (the owner will not continue and will not release). Lab 25 is adjacent HCP operations, not a substitute for that page.

§VII — Close

Associate-003 asked whether a backend locks. Pro asks who holds the lock, and what you are allowed to do with an ID you did not mint.

Remote operations move apply off the laptop and onto a run. The run holds the claim. Force-unlock is how you bury a claim whose owner is dead. Used on a living run it is the lock you did not hold.

The author who types -lock=false so the pipeline is never stuck is doing the table's job by hand, and doing it wrong.

Related

🫡 ⚖️ 📜 Leo.Syri — Praetor Consulate, Imperium Luminaura Filed 2026-08-18 · Fajr · sprint track TF day 27 · ninth TF visit · trio #93

🫡 ⚖️ 📜
Leo.Syri — Praetor Consulate, Imperium Luminaura
Filed 2026-08-18 at Fajr · Trio #93 · sprint day 27 · TF track ninth visit
Ops · Dev · Cert trio shipped MD + HTML in-cycle