hashicorp/terraform · critical

error uploading state

Error message

error uploading state: %w

What it means

Thrown during PersistState when the uploadState method fails to push the serialized state (raw, JSON, and JSON outputs) to HCP Terraform / TFE via the StateVersions.Upload API. On failure, s.stateUploadErr is set to true, which intentionally prevents the workspace from being unlocked so that a subsequent apply cannot run against stale state. This is the primary network/authorization error for remote state persistence.

Solutions

  1. Check network connectivity and DNS resolution to your HCP Terraform / TFE endpoint
  2. Verify the API token is valid and not expired (terraform login or TF_TOKEN / credentials)
  3. Ensure no other process is applying to the same workspace concurrently (check the run queue and workspace lock)
  4. Confirm the authenticated user/team has write permissions on the workspace
  5. Retry the operation after a brief wait if the wrapped error indicates a transient 5xx
  6. If on self-hosted TFE, check the TFE instance health and that the state version upload API is supported
Defensive patterns

Strategy: retry

Validate before calling

// Before persisting, verify the TFE client can reach the workspace:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if _, err := tfeClient.Workspaces.Read(ctx, organization, workspaceName); err != nil {
    return fmt.Errorf("pre-persist workspace check failed, upload would likely fail: %w", err)
}

Try / catch

// Retry PersistState for transient upload errors, but surface conflicts:
backoff := time.Second
for attempt := 0; attempt < 3; attempt++ {
    err := stateMgr.PersistState(schemas)
    if err == nil {
        break
    }
    msg := err.Error()
    if strings.Contains(msg, "409") || strings.Contains(msg, "conflict") {
        return err // lineage/serial conflict — do NOT retry blindly
    }
    if isTransientError(msg) {
        time.Sleep(backoff)
        backoff *= 2
        continue
    }
    return err
}

Prevention

When it happens

Trigger: Network failure or timeout during the state version upload HTTP request; invalid or expired API token; workspace locked by another run causing a 409 conflict; lineage/serial mismatch (concurrent modification); TFE server 5xx error; workspace permissions insufficient for the authenticated user; old TFE version lacking the Upload endpoint triggering the fallback path which also fails.

Common situations: Intermittent network connectivity to app.terraform.io or a self-hosted TFE instance; expired/rotated API tokens after credential management changes; two CI pipelines running apply concurrently against the same workspace; TFE instance under load returning 502/503; user with read-only workspace membership attempting apply.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/dd062ccdf7b67ac7. Report an issue: GitHub.

Appendix: source

Thrown at internal/cloud/state.go:234

	stateFile, err := statefile.Read(bytes.NewReader(buf.Bytes()))
	if err != nil {
		return fmt.Errorf("failed to read state: %w", err)
	}

	ov, err := jsonstate.MarshalOutputs(stateFile.State.RootOutputValues)
	if err != nil {
		return fmt.Errorf("failed to translate outputs: %w", err)
	}
	jsonStateOutputs, err := json.Marshal(ov)
	if err != nil {
		return fmt.Errorf("failed to marshal outputs to json: %w", err)
	}

	err = s.uploadState(s.lineage, s.serial, s.forcePush, buf.Bytes(), jsonState, jsonStateOutputs)
	if err != nil {
		s.stateUploadErr = true
		return fmt.Errorf("error uploading state: %w", err)
	}
	// After we've successfully persisted, what we just wrote is our new
	// reference state until someone calls RefreshState again.
	// We've potentially overwritten (via force) the state, lineage
	// and / or serial (and serial was incremented) so we copy over all
	// three fields so everything matches the new state and a subsequent
	// operation would correctly detect no changes to the lineage, serial or state.
	s.readState = s.state.DeepCopy()
	s.readLineage = s.lineage
	s.readSerial = s.serial

	return nil
}

// ShouldPersistIntermediateState implements statemgr.IntermediateStateConditionalPersister
func (s *State) ShouldPersistIntermediateState(info *statemgr.IntermediateStatePersistInfo) bool {
	if info.ForcePersist {
		return true

View on GitHub (pinned to d32a084675)