gastownhall/beads · error

issue %s waiters: %w

Error message

issue %s waiters: %w

What it means

During migration from a legacy SQLite database, the reader validates the `waiters` JSON column of each issue. This error wraps a failure from either checkJSONSurrogates (invalid Unicode surrogate pairs) or decodeWaiters (JSON that is valid but does not decode into the expected waiters structure). It means the stored waiters value cannot be safely carried into the current schema.

Source

Thrown at internal/migration/legacysqlite/reader.go:525

// metadata is stored verbatim unless it is the empty object; waiters is decoded.
func (x legacyExtras) applyMetadataAndWaiters(issue *types.Issue) error {
	if x.metadata.Valid && x.metadata.String != "" && !json.Valid([]byte(x.metadata.String)) {
		return fmt.Errorf("legacy SQLite issue %s has invalid metadata JSON", issue.ID)
	}
	if x.metadata.Valid && x.metadata.String != "" {
		if err := checkJSONSurrogates(x.metadata.String); err != nil {
			return fmt.Errorf("legacy SQLite issue %s metadata: %w", issue.ID, err)
		}
	}
	if x.metadata.Valid && x.metadata.String != "" && x.metadata.String != "{}" {
		issue.Metadata = []byte(x.metadata.String)
	}
	if x.waiters.Valid && x.waiters.String != "" {
		if !json.Valid([]byte(x.waiters.String)) {
			return fmt.Errorf("issue %s waiters: invalid JSON", issue.ID)
		}
		if err := checkJSONSurrogates(x.waiters.String); err != nil {
			return fmt.Errorf("issue %s waiters: %w", issue.ID, err)
		}
		waiters, err := decodeWaiters(x.waiters.String)
		if err != nil {
			return fmt.Errorf("issue %s waiters: %w", issue.ID, err)
		}
		issue.Waiters = waiters
	}
	return nil
}

// applyCanonicalTimestamps normalizes the required created_at/updated_at values
// to the canonical current-schema representation.
func (x legacyExtras) applyCanonicalTimestamps(issue *types.Issue) error {
	var err error
	if issue.CreatedAt, err = canonicalCurrentDatetime(issue.CreatedAt); err != nil {
		return fmt.Errorf("legacy SQLite issue %s created_at: %w", issue.ID, err)
	}
	if issue.UpdatedAt, err = canonicalCurrentDatetime(issue.UpdatedAt); err != nil {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Open the legacy SQLite DB and locate the offending issue ID shown in the message, then inspect its waiters column: sqlite3 legacy.db "SELECT id, waiters FROM issues WHERE id='<id>'"
  2. Fix or clear the waiters value (set it to NULL or valid JSON, e.g. '[]') for that row
  3. Re-encode any surrogate-escaped strings to proper UTF-8 before importing (e.g. re-dump the DB through a tool that normalizes text)
  4. Re-run the migration

Example fix

// before (invalid: lone surrogate)
{"waiters": "[{\"name\": \"\ud83d\"}]"}
// after (valid UTF-8, correct shape)
{"waiters": "[]"}
Defensive patterns

Strategy: validation

Validate before calling

import json
def validate_waiters(raw):
    if raw is None or raw == '':
        return True
    try:
        json.loads(raw)
        return True
    except (json.JSONDecodeError, ValueError):
        return False
# run against every row before migrating:
# bad = [row['id'] for row in rows if not validate_waiters(row['waiters'])]

Type guard

def is_valid_waiters(raw) -> bool:
    if not raw:
        return True
    if not isinstance(raw, str) or not json_valid(raw):
        return False
    v = json.loads(raw)
    return isinstance(v, list)

Prevention

When it happens

Trigger: Running `bd migrate` (or the legacy-SQLite validation path) against a legacy database where an issue's waiters column holds JSON with lone/unpaired Unicode surrogates, or structurally valid JSON whose shape does not match the expected waiters array (e.g. an object or string instead of a list of waiter entries).

Common situations: Databases written by older beads versions or third-party tools that stored hand-edited or truncated JSON in the waiters column; data produced on platforms that allowed invalid surrogate escapes (\uD800 without a pair) to be written verbatim.

Related errors


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