vitessio/vitess · error

stream %d for %v.%v is not in Stopped after copy state: %v,

Error message

stream %d for %v.%v is not in Stopped after copy state: %v, %v

What it means

When the vindex being externalized has an owner and StopAfterCopy is set, Wrangler requires every stream to be in Stopped state with a message containing 'Stopped after copy'. Anything else means the streams are not in the expected post-copy quiescent state.

Source

Thrown at go/vt/wrangler/materializer.go:892

			message := row[2].ToString()
			var bls binlogdatapb.BinlogSource
			sourceBytes, err := row[3].ToBytes()
			if err != nil {
				return err
			}
			if err := prototext.Unmarshal(sourceBytes, &bls); err != nil {
				return err
			}
			if sourceVindex.Owner == "" || !bls.StopAfterCopy {
				// If there's no owner or we've requested that the workflow NOT be stopped
				// after the copy phase completes, then all streams need to be running.
				if state != binlogdatapb.VReplicationWorkflowState_Running {
					return fmt.Errorf("stream %d for %v.%v is not in Running state: %v", id, targetShard.Keyspace(), targetShard.ShardName(), state)
				}
			} else {
				// If there is an owner, all streams need to be stopped after copy.
				if state != binlogdatapb.VReplicationWorkflowState_Stopped || !strings.Contains(message, "Stopped after copy") {
					return fmt.Errorf("stream %d for %v.%v is not in Stopped after copy state: %v, %v", id, targetShard.Keyspace(), targetShard.ShardName(), state, message)
				}
			}
		}
		return nil
	})
	if err != nil {
		return err
	}

	if sourceVindex.Owner != "" {
		// If there is an owner, we have to delete the streams.
		err := forAllTargets(func(targetShard *topo.ShardInfo) error {
			targetPrimary, err := wr.ts.GetTablet(ctx, targetShard.PrimaryAlias)
			if err != nil {
				return err
			}
			query := fmt.Sprintf("delete from _vt.vreplication where db_name=%s and workflow=%s", encodeString(targetPrimary.DbName()), encodeString(workflow))
			_, err = wr.tmc.VReplicationExec(ctx, targetPrimary.Tablet, query)

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Wait until the copy phase finishes and the streams auto-stop (message shows 'Stopped after copy'), verified via Workflow Show.
  2. If streams are still running, let them catch up; do not stop them manually.
  3. If a stream is in Error, fix the cause and restart the workflow so it can complete copy and stop correctly.
  4. Re-run ExternalizeVindex once all streams report Stopped with 'Stopped after copy'.

Example fix

// before
vtctldclient Workflow --workflow corder_vdx_vdx Stop
vtctldclient ExternalizeVindex commerce.corder_vdx  // wrong stop reason
// after: let workflow stop itself after copy, then
vtctldclient ExternalizeVindex commerce.corder_vdx
Defensive patterns

Strategy: validation

Validate before calling

for _, s := range streams {
  if s.State != binlogdatapb.VReplicationWorkflowState_Stopped ||
     !strings.Contains(s.Message, "Stopped after copy") {
    return fmt.Errorf("stream %d not ready: %s / %s", s.Id, s.State, s.Message)
  }
}

Try / catch

if err := wr.ExternalizeVindex(ctx, name); err != nil {
  if strings.Contains(err.Error(), "Stopped after copy") {
    // wait for copy phase to complete and auto-stop before retrying
  }
}

Prevention

When it happens

Trigger: ExternalizeVindex called for an owned vindex whose streams are still Running/Copying, or were stopped for another reason (manual stop, error) so the message lacks 'Stopped after copy'.

Common situations: Impatiently externalizing before the auto-stop-after-copy completed; streams stopped manually via Workflow Stop instead of letting the copy phase complete; a stream errored and was stopped.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/550829c71da03aab. Report an issue: GitHub.