{"record":{"id":"616acbaeef92ab35","repo":"gastownhall/beads","slug":"pattern-q-is-not-a-valid-glob-v","errorCode":null,"errorMessage":"--pattern %q is not a valid glob: %v","messagePattern":"--pattern %q is not a valid glob: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/workapi/sweep.go","lineNumber":46,"sourceCode":"// it lives HERE rather than in a CLI handler: a second front door inherits it\n// by calling the role. See issueops.SweepRequest.\nfunc ValidateSweepRequest(in issueops.SweepRequest) error {\n\tswitch in.Tier {\n\tcase issueops.SweepEphemeral, issueops.SweepDurable:\n\tcase \"\":\n\t\treturn fmt.Errorf(\"%w: sweep requires a tier (%q or %q), and has no default\",\n\t\t\tissueops.ErrValidation, issueops.SweepEphemeral, issueops.SweepDurable)\n\tdefault:\n\t\treturn fmt.Errorf(\"%w: %q is not a sweep tier; use %q or %q\",\n\t\t\tissueops.ErrValidation, in.Tier, issueops.SweepEphemeral, issueops.SweepDurable)\n\t}\n\tif in.IDPattern != \"\" {\n\t\t// filepath.Match reports a malformed pattern on any subject, so one\n\t\t// probe against the empty string classifies the pattern itself. The\n\t\t// front doors used to discard this error, which turned `--pattern '['`\n\t\t// into \"nothing matched\" on a command whose job is to delete matches.\n\t\tif _, err := filepath.Match(in.IDPattern, \"\"); err != nil {\n\t\t\treturn fmt.Errorf(\"%w: --pattern %q is not a valid glob: %v\",\n\t\t\t\tissueops.ErrValidation, in.IDPattern, err)\n\t\t}\n\t}\n\tif in.Tier == issueops.SweepDurable && in.ClosedBefore == nil && in.IDPattern == \"\" {\n\t\treturn fmt.Errorf(\"%w: a durable sweep requires a closed-before cutoff or an id pattern; \"+\n\t\t\t\"pass the pattern \\\"*\\\" to sweep every closed issue deliberately\",\n\t\t\tissueops.ErrValidation)\n\t}\n\treturn nil\n}\n\n// BuildSweepCandidateFilter turns a sweep request into the storage-level\n// filter that selects its CANDIDATES: the closed rows of one tier, bounded by\n// the cutoff.\n//\n// The pattern is deliberately NOT in the filter. Globs are matched in Go\n// (MatchesSweepPattern) because a LIKE translation would silently disagree with\n// filepath.Match on `[...]` and on the escape rules — a disagreement that, on","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/workapi/sweep.go#L28-L64","documentation":"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.","triggerScenarios":"Calling a Sweeper with issueops.SweepRequest{Tier: ..., IDPattern: \"[\"} or any other pattern filepath.Match rejects (unterminated character class, trailing backslash, etc.).","commonSituations":"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.","solutions":["Fix the glob pattern so filepath.Match accepts it (balance brackets, escape specials)","Validate with filepath.Match(pattern, \"\") in the caller before constructing the request for an earlier, clearer failure","Prefer the deliberate \"*\" pattern (or no pattern) over hand-built globs when sweeping everything closed","Handle errors.Is(err, issueops.ErrValidation) and echo the underlying filepath.Match error"],"exampleFix":"// before\nreq := issueops.SweepRequest{Tier: issueops.SweepDurable, IDPattern: \"bd-[0-9\"}\n// after\nreq := issueops.SweepRequest{Tier: issueops.SweepDurable, IDPattern: \"bd-[0-9]*\"}","handlingStrategy":"validation","validationCode":"if req.IDPattern != \"\" {\n    if _, err := filepath.Match(req.IDPattern, \"\"); err != nil {\n        return fmt.Errorf(\"pattern %q is not a valid glob: %v\", req.IDPattern, err)\n    }\n}","typeGuard":"func isValidGlob(pattern string) bool {\n    _, err := filepath.Match(pattern, \"\")\n    return err == nil\n}","tryCatchPattern":"if err := sweeper.Sweep(ctx, req); err != nil {\n    if errors.Is(err, issueops.ErrValidation) {\n        return fmt.Errorf(\"bad sweep pattern: %w\", err)\n    }\n    return err\n}","preventionTips":["Pre-validate user globs with filepath.Match(pattern, \"\") at the CLI boundary","Quote globs in shell usage docs so brackets are not consumed by the shell","Prefer the literal \"*\" pattern over constructed globs when sweeping everything","Remember a malformed pattern previously meant 'nothing matched' on a destructive command — never swallow filepath.Match errors"],"tags":["go","validation","sweep","glob","filepath-match"],"backgroundTag":"invalid-glob-pattern","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}