gastownhall/beads · error

expected digit at position %d

Error message

expected digit at position %d

What it means

A token starting with '-' or '+' was not followed by a digit. readNumberOrDuration (internal/query/lexer.go:253) only treats sign-led tokens as numbers/durations, so '-' or '+' followed by an identifier character (e.g. `-alpha`) is rejected. Unsigned digit-led runs with identifier continuations are tolerated (re-lexed as identifiers), but signed forms must be numeric.

Source

Thrown at internal/query/lexer.go:253

// they can stand in unquoted on the value side of comparisons like
// "label=1-alpha". Signed forms ("-3-foo") still error — the user has
// to quote them.
func (l *Lexer) readNumberOrDuration(startPos int) (Token, error) {
	var sb strings.Builder

	// Handle optional sign
	r := l.next()
	hadSign := r == '-' || r == '+'
	if hadSign {
		sb.WriteRune(r)
		r = l.next()
	}

	// Must have at least one digit
	if !unicode.IsDigit(r) {
		l.backup()
		// This might be a minus in front of an identifier, which is invalid
		return Token{}, fmt.Errorf("expected digit at position %d", l.pos)
	}
	sb.WriteRune(r)

	// Read remaining digits
	for {
		r = l.next()
		if !unicode.IsDigit(r) {
			break
		}
		sb.WriteRune(r)
	}

	// Check for duration suffix. Only commit to a duration when the suffix
	// stands alone — if more identifier characters follow (e.g. "7day"),
	// fall through to the identifier-fallback below.
	if r != 0 && isDurationSuffix(r) && !isIdentChar(l.peek()) {
		sb.WriteRune(r)
		return Token{Type: TokenDuration, Value: sb.String(), Pos: startPos}, nil

View on GitHub (pinned to 71377f2769)

Solutions

  1. Quote sign-led values: `label="-alpha"` (comment in lexer.go confirms signed forms must be quoted).
  2. If you meant exclusion, there is no `-` exclusion operator — use `NOT label=foo`.
  3. If the value is hyphenated but starts with a digit (e.g. `1-alpha`), it is already accepted unquoted; only leading signs need quoting.
  4. Check the reported position: it points just past the sign, where a digit was expected.

Example fix

// before
query.Parse("label=-alpha")
// after
query.Parse("label=\"-alpha\"")
Defensive patterns

Strategy: validation

Validate before calling

// Quote sign-led values before parsing
func quoteSignedValues(q string) string {
    return regexp.MustCompile(`(=|!=|<|<=|>|>=)\s*([+-][A-Za-z_][\w./:-]*)`).ReplaceAllString(q, "$1\"$2\"")
}

Try / catch

if _, err := query.Parse(q); err != nil && strings.Contains(err.Error(), "expected digit") {
    return fmt.Errorf("values starting with - or + must be quoted: %w", err)
}

Prevention

When it happens

Trigger: query.Parse with a sign-led non-numeric value: `label=-alpha`, `status=-new`, `priority=+high`, `assignee=-me`, or a negated bare word like `NOT -foo=1`.

Common situations: Users writing shell-style `-flag` prefixes inside queries; values that legitimately begin with '-' (hyphenated labels like `-experimental`) supplied unquoted; minus used to mean exclusion (`-label=foo`) as in other search DSLs.

Related errors


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