hashicorp/terraform · error

error uploading state: %v

Error message

error uploading state: %v

What it means

Emitted by remoteClient.Put (backend_state.go:154-157) when the primary StateVersions.Upload fails for any reason other than ErrStateVersionUploadNotSupported (which diverts to the compatibility path). It sets r.stateUploadErr=true so the workspace lock is intentionally retained to block applies until consistent state is restored.

Source

Thrown at internal/backend/remote/backend_state.go:156

	}

	// If we have a run ID, make sure to add it to the options
	// so the state will be properly associated with the run.
	if r.runID != "" {
		options.Run = &tfe.Run{ID: r.runID}
	}

	// Create the new state.
	// Create the new state.
	_, err = r.client.StateVersions.Upload(ctx, r.workspace.ID, options)
	if errors.Is(err, tfe.ErrStateVersionUploadNotSupported) {
		// Create the new state with content included in the request (Terraform Enterprise v202306-1 and below)
		log.Println("[INFO] Detected that state version upload is not supported. Retrying using compatibility state upload.")
		return diags.Append(r.uploadStateFallback(ctx, stateFile, state, o))
	}
	if err != nil {
		r.stateUploadErr = true
		return diags.Append(fmt.Errorf("error uploading state: %v", err))
	}

	return nil
}

// Delete the remote state.
func (r *remoteClient) Delete() tfdiags.Diagnostics {
	var diags tfdiags.Diagnostics
	err := r.client.Workspaces.Delete(context.Background(), r.organization, r.workspace.Name)
	if err != nil && err != tfe.ErrResourceNotFound {
		return diags.Append(fmt.Errorf("error deleting workspace %s: %v", r.workspace.Name, err))
	}

	return nil
}

// EnableForcePush to allow the remote client to overwrite state
// by implementing remote.ClientForcePusher

View on GitHub (pinned to c9def3e214)

Solutions

  1. Inspect the wrapped %v: a serial/lineage conflict means a newer state exists remotely — run `terraform state pull`, reconcile, and re-push rather than forcing.
  2. Use `-lock-timeout` and ensure only one writer per workspace to avoid serial races.
  3. Refresh the token (`terraform login`) if the cause is 401/403.
  4. If the workspace is now stuck locked due to stateUploadErr, run `terraform force-unlock` only after confirming state consistency.

Example fix

// before: concurrent writers caused serial conflict
Error: error uploading state: 409 Conflict: serial 12 already exists

// after: serialize writes with locking
$ terraform apply -lock-timeout=120s
Defensive patterns

Strategy: retry

Validate before calling

// Preflight: confirm the remote serial/lineage so you can detect a conflict before upload.
if cur, err := r.client.StateVersions.ReadCurrent(ctx, r.workspace.ID); err == nil && cur.Serial >= int64(stateFile.Serial) {
    if !r.forcePush { return fmt.Errorf("remote serial %d >= local %d; refusing non-force upload", cur.Serial, stateFile.Serial) }
}

Try / catch

// Retry transient errors; surface conflicts distinctly so the operator reconciles state.
_, err = r.client.StateVersions.Upload(ctx, r.workspace.ID, options)
if errors.Is(err, tfe.ErrStateVersionUploadNotSupported) { return r.uploadStateFallback(ctx, stateFile, state, o) }
if err != nil {
    r.stateUploadErr = true
    // do not unlock; return error so lock is retained
}

Prevention

When it happens

Trigger: The TFC/TFE Upload endpoint returns an error: 401/403 auth or permission, 409 lineage/serial conflict (state pushed by another process), 413 payload too large, MD5 mismatch, 5xx server error, or network failure.

Common situations: Two CI jobs racing to write the same workspace (serial conflict); expired token; state grew past server limits; transient TFC 5xx; MD5 mismatch from local state corruption; running apply while a concurrent plan/apply uploaded newer state.

Related errors


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/57b27584b111c5d9. Report an issue: GitHub.