gastownhall/beads · error

--pattern %q is not a valid glob: %v

Error message

--pattern %q is not a valid glob: %v

What it means

ValidateSweepRequest probes the IDPattern with filepath.Match and refuses malformed glob patterns. filepath.Match reports a malformed pattern on any subject, so one probe against the empty string classifies the pattern itself. This matters because front doors used to discard the error, turning a pattern like '[' into 'nothing matched' on a command whose job is to delete matches — a silent no-op on a destructive operation.

Source

Thrown at internal/workapi/sweep.go:46

// it lives HERE rather than in a CLI handler: a second front door inherits it
// by calling the role. See issueops.SweepRequest.
func ValidateSweepRequest(in issueops.SweepRequest) error {
	switch in.Tier {
	case issueops.SweepEphemeral, issueops.SweepDurable:
	case "":
		return fmt.Errorf("%w: sweep requires a tier (%q or %q), and has no default",
			issueops.ErrValidation, issueops.SweepEphemeral, issueops.SweepDurable)
	default:
		return fmt.Errorf("%w: %q is not a sweep tier; use %q or %q",
			issueops.ErrValidation, in.Tier, issueops.SweepEphemeral, issueops.SweepDurable)
	}
	if in.IDPattern != "" {
		// filepath.Match reports a malformed pattern on any subject, so one
		// probe against the empty string classifies the pattern itself. The
		// front doors used to discard this error, which turned `--pattern '['`
		// into "nothing matched" on a command whose job is to delete matches.
		if _, err := filepath.Match(in.IDPattern, ""); err != nil {
			return fmt.Errorf("%w: --pattern %q is not a valid glob: %v",
				issueops.ErrValidation, in.IDPattern, err)
		}
	}
	if in.Tier == issueops.SweepDurable && in.ClosedBefore == nil && in.IDPattern == "" {
		return fmt.Errorf("%w: a durable sweep requires a closed-before cutoff or an id pattern; "+
			"pass the pattern \"*\" to sweep every closed issue deliberately",
			issueops.ErrValidation)
	}
	return nil
}

// BuildSweepCandidateFilter turns a sweep request into the storage-level
// filter that selects its CANDIDATES: the closed rows of one tier, bounded by
// the cutoff.
//
// The pattern is deliberately NOT in the filter. Globs are matched in Go
// (MatchesSweepPattern) because a LIKE translation would silently disagree with
// filepath.Match on `[...]` and on the escape rules — a disagreement that, on

View on GitHub (pinned to 71377f2769)

Solutions

  1. Fix the glob pattern so filepath.Match accepts it (balance brackets, escape specials)
  2. Validate with filepath.Match(pattern, "") in the caller before constructing the request for an earlier, clearer failure
  3. Prefer the deliberate "*" pattern (or no pattern) over hand-built globs when sweeping everything closed
  4. Handle errors.Is(err, issueops.ErrValidation) and echo the underlying filepath.Match error

Example fix

// before
req := issueops.SweepRequest{Tier: issueops.SweepDurable, IDPattern: "bd-[0-9"}
// after
req := issueops.SweepRequest{Tier: issueops.SweepDurable, IDPattern: "bd-[0-9]*"}
Defensive patterns

Strategy: validation

Validate before calling

if req.IDPattern != "" {
    if _, err := filepath.Match(req.IDPattern, ""); err != nil {
        return fmt.Errorf("pattern %q is not a valid glob: %v", req.IDPattern, err)
    }
}

Type guard

func isValidGlob(pattern string) bool {
    _, err := filepath.Match(pattern, "")
    return err == nil
}

Try / catch

if err := sweeper.Sweep(ctx, req); err != nil {
    if errors.Is(err, issueops.ErrValidation) {
        return fmt.Errorf("bad sweep pattern: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling a Sweeper with issueops.SweepRequest{Tier: ..., IDPattern: "["} or any other pattern filepath.Match rejects (unterminated character class, trailing backslash, etc.).

Common situations: A user typed --pattern '[' or 'bd-[0-9' on the command line; a shell ate a closing bracket leaving an unbalanced one; a caller interpolated an id prefix into a pattern template and broke the bracket syntax.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/616acbaeef92ab35. Report an issue: GitHub.