gastownhall/beads · error
legacy SQLite issue %s has invalid metadata JSON
Error message
legacy SQLite issue %s has invalid metadata JSON
What it means
applyMetadataAndWaiters checks that a legacy issue's non-empty metadata column is well-formed JSON via json.Valid; if not, it reports 'legacy SQLite issue <id> has invalid metadata JSON'. Valid JSON is required because metadata is stored verbatim into the new issue record.
Source
Thrown at internal/migration/legacysqlite/reader.go:510
}
if issue.CompactedAt, err = parseOptionalTime("compacted_at", x.compactedAt); err != nil {
return fmt.Errorf("legacy SQLite issue %s: %w", issue.ID, err)
}
if issue.DueAt, err = parseOptionalTime("due_at", x.dueAt); err != nil {
return fmt.Errorf("legacy SQLite issue %s: %w", issue.ID, err)
}
if issue.DeferUntil, err = parseOptionalTime("defer_until", x.deferUntil); err != nil {
return fmt.Errorf("legacy SQLite issue %s: %w", issue.ID, err)
}
return nil
}
// 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 {View on GitHub (pinned to 71377f2769)
Solutions
- Inspect the metadata column for the reported issue ID and repair it into valid JSON (or set it to NULL/'{}'), then re-run migration.
- Re-serialize the intended key/value pairs into a valid JSON object before migrating.
- Clear the corrupted metadata (NULL) if it can be reconstructed after migration.
Example fix
-- before
UPDATE issues SET metadata='{"key": ' WHERE id='bd-3';
-- after
UPDATE issues SET metadata='{"key": "value"}' WHERE id='bd-3'; Defensive patterns
Strategy: validation
Validate before calling
rows, _ := db.Query(`SELECT id FROM issues WHERE metadata IS NOT NULL AND metadata != '' `)
for rows.Next() {
var id, meta string
rows.Scan(&id, &meta)
if !json.Valid([]byte(meta)) { /* repair before migrate */ }
} Try / catch
if !json.Valid([]byte(metadata)) {
// re-serialize or NULL the metadata column before migrating
} Prevention
- Never append/edit metadata TEXT columns by hand.
- Check for truncated writes (disk full, killed process) after legacy DB incidents.
- Run a json.Valid audit over the metadata column before migrating.
- Serialize metadata programmatically, never via string concatenation.
When it happens
Trigger: Migrating a legacy DB where an issues.metadata column holds text that is not parseable JSON (truncated blob, plain string, concatenated fragments).
Common situations: Truncated TEXT columns from interrupted writes; manual edits appending prose to the JSON; encoding bugs in old exporter tools; NULL-terminator or BOM artifacts breaking the parser.
Related errors
- issue %s waiters: invalid 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/204c8e74237f4d1b.
Report an issue: GitHub.