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

  1. Ensure no concurrent terraform apply/plan against the same workspace and re-run; Terraform will re-Read, refresh modifyIndex, and retry the CAS.
  2. Inspect the wrapped errors (resp.Errors[].What) in Consul logs for the specific sub-error.
  3. Verify the backend ACL token has kv:write on the state prefix.
  4. 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

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


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 diags

View on GitHub (pinned to d32a084675)