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.ClientForcePusherView on GitHub (pinned to c9def3e214)
Solutions
- 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.
- Use `-lock-timeout` and ensure only one writer per workspace to avoid serial races.
- Refresh the token (`terraform login`) if the cause is 401/403.
- 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
- Serialize writes per workspace with `-lock-timeout` to avoid serial races.
- Inspect the wrapped cause: 409 -> reconcile state, 401/403 -> refresh token, 5xx -> retry.
- Only force-push (forcePush=true) when you understand the lineage/serial implications.
- Run `terraform force-unlock` only after confirming state consistency post-failure.
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
- %s (lock ID: "%s/%s")
- Error retrieving state: %v
- Error downloading state: %v
- lock ID does not match existing lock
- lock ID %q does not match existing lock ID "%s/%s"
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/57b27584b111c5d9.
Report an issue: GitHub.