hashicorp/terraform · error
consul CAS failed with transaction errors
Error message
consul CAS failed with transaction errors: %w
What it means
In RemoteClient.Put the state is written through a single-op Consul transaction using a CAS verb keyed off c.modifyIndex. If Consul rolls the transaction back (ok==false), each resp.Errors entry is joined into resultErr and wrapped. The dominant cause is a CAS failure: the key's ModifyIndex changed since the client last read it, so the compare-and-set precondition failed.
Solutions
- Ensure no concurrent terraform apply/plan against the same workspace and re-run; Terraform will re-Read, refresh modifyIndex, and retry the CAS.
- Inspect the wrapped errors (resp.Errors[].What) in Consul logs for the specific sub-error.
- Verify the backend ACL token has kv:write on the state prefix.
- If the error names size limits, expect the client to retry in chunked mode; otherwise treat as concurrency.
Defensive patterns
Strategy: retry
Try / catch
// CAS failures are typically transient concurrency; re-read and retry.
const maxRetries = 5
var lastErr error
for i := 0; i < maxRetries; i++ {
diags := remoteClient.Put(data)
if !diags.HasErrors() {
return nil
}
lastErr = diags.Err()
if !strings.Contains(lastErr.Error(), "CAS failed") {
return lastErr // not a CAS issue
}
time.Sleep(time.Duration(100<<i) * time.Millisecond) // backoff
}
return fmt.Errorf("state write failed after %d CAS retries: %w", maxRetries, lastErr) Prevention
- Serialize Terraform runs per workspace (CI locks, Buildkite concurrency caps).
- Re-run on CAS failures rather than assuming data loss.
- Keep the backend ACL token's kv:write privilege current.
- Watch Consul transaction error rates as a leading indicator of contention.
When it happens
Trigger: store(payload) -> kv.Txn(txOps, nil) returns ok==false; the loop joins resp.Errors into resultErr and the function returns the wrapped error.
Common situations: Two Terraform processes (or an external writer) updated the same state path concurrently; a long-lived Terraform process has a stale in-memory modifyIndex; Consul ACL denies the kv:write at transaction time; a chunked cleanup or large txn exceeded limits and was rolled back.
Related errors
- Error locking state
- Error saving temporary state
- Error unlocking Consul state. Lock ID
- error unmarshaling lock info
- expected on 1 response value, got
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/f7371eaf6f87765f.
Report an issue: GitHub.
Appendix: source
Thrown at internal/backend/remote-state/consul/client.go:246
&consulapi.KVTxnOp{
Verb: verb,
Key: c.Path,
Value: payload,
Index: c.modifyIndex,
},
}
ok, resp, _, err := kv.Txn(txOps, nil)
if err != nil {
return err
}
// transaction was rolled back
if !ok {
var resultErr error
for _, respError := range resp.Errors {
resultErr = errors.Join(resultErr, errors.New(respError.What))
}
return fmt.Errorf("consul CAS failed with transaction errors: %w", resultErr)
}
if len(resp.Results) != 1 {
// this probably shouldn't happen
return fmt.Errorf("expected on 1 response value, got: %d", len(resp.Results))
}
c.modifyIndex = resp.Results[0].ModifyIndex
// We remove all the old chunks
cleanupOldChunks()
return nil
}
if err = store(payload); err == nil {
// The payload was small enough to be stored
return diagsView on GitHub (pinned to d32a084675)