thanos-io/thanos · error

parse block label matchers

Error message

parse block label matchers

What it means

The `bucket replicate` command parses --match (matcherStrs) via replicate.ParseFlagMatchers, which requires each flag value to be a valid Prometheus label matcher of the form key="value" or key=~"regex". This error wraps the parse failure, so at least one matcher string is malformed.

Solutions

  1. Quote the whole flag value so inner quotes survive: --match='block_labels="set=whole"'.
  2. Validate any regex in =~ matchers with a regex tester (balanced delimiters, no dangling + or *).
  3. Check the wrapped inner error — it names the first matcher that failed.
  4. Verify syntax against Prometheus matcher format: key="val", key!="val", key=~"re", key!~"re".

Example fix

// before
--match=block_labels=set=whole
// after
--match='block_labels="set=whole"'
Defensive patterns

Strategy: validation

Validate before calling

for _, m := range matcherStrs {
    ms, err := parser.ParseMetricSelector("{" + m + "}")
    if err != nil {
        return fmt.Errorf("--match value %q is not a valid matcher: %w", m, err)
    }
    _ = ms
}

Try / catch

if err := replicateCmd.RunE(cmd, args); err != nil {
    if strings.Contains(err.Error(), "parse block label matchers") {
        fmt.Fprintln(os.Stderr, "--match must look like: --match='block_labels=\"set=whole\"'")
    }
}

Prevention

When it happens

Trigger: Running `thanos tools bucket replicate` with --match values that fail to parse as label matchers — missing quotes, invalid regex in =~ matchers, empty label name — at cmd/thanos/tools_bucket.go:770.

Common situations: Shell quoting issues removing inner double quotes (--match=block_labels="set=whole"), invalid regex like '--match=l=~"[unclosed"', or using --selector-style relabel syntax where matchers are expected.

Understand the failure class

Background: "Unknown argument", "Invalid value", and "must be one of": invalid CLI argument errors explained — this error's family across 35 libraries.

Related errors


AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07). Data as JSON: /api/errors/b0bf4493f23fc916. Report an issue: GitHub.

Appendix: source

Thrown at cmd/thanos/tools_bucket.go:770

func registerBucketReplicate(app extkingpin.AppClause, objStoreConfig *extflag.PathOrContent) {
	cmd := app.Command("replicate", fmt.Sprintf("Replicate data from one object storage to another. NOTE: Currently it works only with Thanos blocks (%v has to have Thanos metadata).", block.MetaFilename))
	httpBindAddr, httpGracePeriod, httpTLSConfig := extkingpin.RegisterHTTPFlags(cmd)
	toObjStoreConfig := extkingpin.RegisterCommonObjStoreFlags(cmd, "-to", false, "The object storage which replicate data to.")

	tbc := &bucketReplicateConfig{}
	tbc.registerBucketReplicateFlag(cmd)

	minTime := model.TimeOrDuration(cmd.Flag("min-time", "Start of time range limit to replicate. Thanos Replicate will replicate only metrics, which happened later than this value. Option can be a constant time in RFC3339 format or time duration relative to current time, such as -1d or 2h45m. Valid duration units are ms, s, m, h, d, w, y.").
		Default("0000-01-01T00:00:00Z"))
	maxTime := model.TimeOrDuration(cmd.Flag("max-time", "End of time range limit to replicate. Thanos Replicate will replicate only metrics, which happened earlier than this value. Option can be a constant time in RFC3339 format or time duration relative to current time, such as -1d or 2h45m. Valid duration units are ms, s, m, h, d, w, y.").
		Default("9999-12-31T23:59:59Z"))
	ids := cmd.Flag("id", "Block to be replicated to the destination bucket. IDs will be used to match blocks and other matchers will be ignored. When specified, this command will be run only once after successful replication. Repeated field").Strings()
	ignoreMarkedForDeletion := cmd.Flag("ignore-marked-for-deletion", "Do not replicate blocks that have deletion mark.").Bool()

	cmd.Setup(func(g *run.Group, logger log.Logger, reg *prometheus.Registry, tracer opentracing.Tracer, _ <-chan struct{}, _ bool) error {
		matchers, err := replicate.ParseFlagMatchers(tbc.matcherStrs)
		if err != nil {
			return errors.Wrap(err, "parse block label matchers")
		}

		var resolutionLevels []compact.ResolutionLevel
		for _, lvl := range tbc.resolutions {
			resolutionLevels = append(resolutionLevels, compact.ResolutionLevel(lvl.Milliseconds()))
		}

		if len(tbc.compactions) == 0 {
			if tbc.compactMin > tbc.compactMax {
				return errors.New("compaction-min must be less than or equal to compaction-max")
			}
			tbc.compactions = []int{}
			for compactionLevel := tbc.compactMin; compactionLevel <= tbc.compactMax; compactionLevel++ {
				tbc.compactions = append(tbc.compactions, compactionLevel)
			}
		}

		blockIDs := make([]ulid.ULID, 0, len(*ids))

View on GitHub (pinned to 35b8b99117)