gastownhall/beads · error
issue %s waiters: invalid JSON
Error message
issue %s waiters: invalid JSON
What it means
applyMetadataAndWaiters validates the legacy issue's waiters column: it must be valid JSON (this error), free of unpaired surrogates, and decodable by decodeWaiters. A non-empty waiters value that json.Valid rejects aborts migration for that issue with 'issue <id> waiters: invalid JSON'.
Source
Thrown at internal/migration/legacysqlite/reader.go:522
// applyMetadataAndWaiters validates the legacy metadata and waiters JSON blobs
// (well-formed and free of unpaired surrogates) and assigns them to issue.
// 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 {View on GitHub (pinned to 71377f2769)
Solutions
- Rewrite the waiters column for the reported issue ID as valid JSON in the expected waiters shape (or NULL), then re-run migration.
- Check the version that wrote the legacy DB and convert the old waiters serialization to the expected JSON format first.
- Clear the corrupted waiters value (NULL) if the waiter relationships can be recreated after migration.
Example fix
-- before UPDATE issues SET waiters='bd-1,bd-2' WHERE id='bd-8'; -- after UPDATE issues SET waiters='["bd-1","bd-2"]' WHERE id='bd-8';
Defensive patterns
Strategy: validation
Validate before calling
rows, _ := db.Query(`SELECT id, waiters FROM issues WHERE waiters IS NOT NULL AND waiters != ''`)
for rows.Next() {
var id, w string
rows.Scan(&id, &w)
if !json.Valid([]byte(w)) { /* convert or NULL before migrate */ }
} Try / catch
if !json.Valid([]byte(waiters)) {
// convert old serialization to JSON or NULL the column before migrating
} Prevention
- Confirm the legacy DB writer version stores waiters as JSON.
- Never paste plain-text ID lists into the waiters column.
- Audit waiters with json.Valid before migrating.
- Back up and test-migrate a copy of the DB first.
When it happens
Trigger: Migrating a legacy DB where an issues.waiters column contains non-JSON text (e.g. a comma-separated ID list, truncated array, or empty braces variant) instead of a valid JSON document.
Common situations: Old beads versions that stored waiters in a different (non-JSON) serialization; truncated writes; manual edits pasting plain-text waiter lists; data munging by external scripts.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- legacy SQLite issue %s has invalid metadata JSON
- legacy SQLite issue %s: %w
- legacy SQLite issue %s metadata: %w
- issue %s waiters: %w
- truncated JSON escape
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/68d812e1060e2d15.
Report an issue: GitHub.