arazzo: 1.0.1 info: title: Terraform Find and Cancel a Stuck Run summary: Locate an in-flight run on a workspace, cancel it, and unlock the workspace. description: >- An operations runbook for the most common HCP Terraform incident: a run wedged in planning or applying that is holding a workspace hostage. The workflow finds the in-flight run on the workspace, reads it to confirm it is genuinely still running, cancels it, polls until the cancellation lands, and unlocks the workspace so the next run can proceed. Every step spells out its request inline so the flow can be read and executed without opening the underlying OpenAPI description. version: 1.0.0 sourceDescriptions: - name: hcpTerraformApi url: ../openapi/hcp-terraform-openapi.yml type: openapi workflows: - workflowId: cancel-stuck-run summary: Cancel an in-progress run on a workspace and release the workspace lock. description: >- Lists the workspace's runs filtered to an in-flight status, reads the newest match, cancels it, waits for the cancellation to settle, and unlocks the workspace. inputs: type: object required: - workspaceId - runStatusFilter properties: workspaceId: type: string description: The workspace identifier holding the stuck run. runStatusFilter: type: string description: The run status to hunt for (e.g. "planning", "applying", "pending"). cancelComment: type: string description: The comment recorded against the cancellation for the audit trail. steps: - stepId: findInFlightRun description: >- List the workspace's runs filtered to the supplied in-flight status, newest first, taking only the most recent match. operationId: ListWorkspaceRuns parameters: - name: workspace_id in: path value: $inputs.workspaceId - name: filter[status] in: query value: $inputs.runStatusFilter - name: page[number] in: query value: 1 - name: page[size] in: query value: 1 successCriteria: - condition: $statusCode == 200 outputs: stuckRunId: $response.body#/data/0/id onSuccess: - name: foundStuckRun type: goto stepId: readRun criteria: - context: $response.body condition: $.data.length > 0 type: jsonpath - name: nothingStuck type: end criteria: - context: $response.body condition: $.data.length == 0 type: jsonpath - stepId: readRun description: >- Read the matched run to confirm it is genuinely still in flight and has not settled on its own since the list call. operationId: GetRun parameters: - name: run_id in: path value: $steps.findInFlightRun.outputs.stuckRunId successCriteria: - condition: $statusCode == 200 outputs: runId: $response.body#/data/id status: $response.body#/data/attributes/status message: $response.body#/data/attributes/message - stepId: cancelRun description: >- Cancel the in-progress run, recording why it was cancelled so the run history explains the intervention. operationId: CancelRun parameters: - name: run_id in: path value: $steps.readRun.outputs.runId requestBody: contentType: application/vnd.api+json payload: comment: $inputs.cancelComment successCriteria: - condition: $statusCode == 202 - stepId: pollCancellation description: >- Poll the run until the cancellation is reflected in its status, repeating while the run is still winding down. operationId: GetRun parameters: - name: run_id in: path value: $steps.readRun.outputs.runId successCriteria: - condition: $statusCode == 200 outputs: finalStatus: $response.body#/data/attributes/status onSuccess: - name: cancelled type: goto stepId: unlockWorkspace criteria: - context: $response.body condition: $.data.attributes.status in ["canceled", "force_canceled", "discarded"] type: jsonpath - name: stillCancelling type: retry stepId: pollCancellation retryAfter: 5 retryLimit: 60 criteria: - context: $response.body condition: $.data.attributes.status in ["pending", "fetching", "planning", "applying"] type: jsonpath - stepId: unlockWorkspace description: >- Unlock the workspace so the next run can be queued, since a cancelled run can leave the workspace locked behind it. operationId: UnlockWorkspace parameters: - name: workspace_id in: path value: $inputs.workspaceId successCriteria: - condition: $statusCode == 200 outputs: locked: $response.body#/data/attributes/locked outputs: cancelledRunId: $steps.readRun.outputs.runId finalStatus: $steps.pollCancellation.outputs.finalStatus workspaceLocked: $steps.unlockWorkspace.outputs.locked