gastownhall/beads · error

unterminated escape sequence at position %d

Error message

unterminated escape sequence at position %d

What it means

Inside a quoted string, a backslash started an escape sequence but the input ended immediately after the backslash (the escaped rune was 0). readString (internal/query/lexer.go:221) reports position l.pos-1, which is the position of the trailing backslash.

Source

Thrown at internal/query/lexer.go:221

		if r == quote {
			return Token{Type: TokenString, Value: sb.String(), Pos: startPos}, nil
		}
		if r == '\\' {
			// Handle escape sequences
			escaped := l.next()
			switch escaped {
			case 'n':
				sb.WriteRune('\n')
			case 't':
				sb.WriteRune('\t')
			case '\\':
				sb.WriteRune('\\')
			case '"':
				sb.WriteRune('"')
			case '\'':
				sb.WriteRune('\'')
			case 0:
				return Token{}, fmt.Errorf("unterminated escape sequence at position %d", l.pos-1)
			default:
				sb.WriteRune(escaped)
			}
		} else {
			sb.WriteRune(r)
		}
	}
}

// readNumberOrDuration reads a number or duration (e.g., 7d, 24h).
//
// Unsigned digit-led tokens that continue into identifier characters
// (e.g. "1-alpha", "42day-sla", "9.3.1") are re-lexed as identifiers so
// 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

View on GitHub (pinned to 71377f2769)

Solutions

  1. Close the string properly: a trailing backslash before EOF is always wrong; end with the closing quote.
  2. Escape literal backslashes by doubling them: `path="C:\\\\Users"`.
  3. If the backslash was meant literally as the final character, write `\\` then the quote: `value="end\\\\"`.
  4. Check shell escaping — an extra shell-level `\\` can consume the query's closing quote, leaving the lexer's backslash dangling at EOF.

Example fix

// before
query.Parse("title=\"report\\") // backslash then EOF
// after
query.Parse("title=\"report\\\\\"") // escaped backslash + closing quote
Defensive patterns

Strategy: validation

Validate before calling

// Reject a string value whose final character before EOF is a dangling backslash
func trailingBackslash(q string) bool {
    return strings.HasSuffix(q, "\\") || strings.HasSuffix(q, "\\'") && !strings.HasSuffix(q, "\\\\'")
}

Try / catch

if _, err := query.Parse(q); err != nil && strings.Contains(err.Error(), "unterminated escape sequence") {
    return fmt.Errorf("query ends with a bare backslash; escape it as \\\\")
}

Prevention

When it happens

Trigger: query.Parse where the string's last character before EOF is a backslash: `title="path\\` (value ends with `\\` at EOF) or `status='open\\`.

Common situations: Windows-style path values cut off (`C:\\Users\\...` truncated); a value ending in a backslash where the author forgot the lexer requires escaping (`\\` for a literal backslash) and the closing quote was also lost; template-generated queries where a trailing backslash fell out of variable interpolation.

Related errors


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