gastownhall/beads · error
must be an array of strings
Error message
must be an array of strings
What it means
decodeWaiters parses the legacy waiters column as JSON and requires the top-level value to be a JSON array. If json.Unmarshal succeeds but the value is not an array (object, string, number, etc.), the migration returns 'must be an array of strings'.
Source
Thrown at internal/migration/legacysqlite/reader.go:695
func validateCurrentTextBytes(issue *types.Issue) error {
if len(issue.Payload) > currentTextBytes {
return fmt.Errorf("legacy SQLite issue payload is %d bytes (current TEXT maximum %d)", len(issue.Payload), currentTextBytes)
}
waiters := issueops.FormatJSONStringArray(issue.Waiters)
if len(waiters) > currentTextBytes {
return fmt.Errorf("legacy SQLite issue waiters serialize to %d bytes (current TEXT maximum %d)", len(waiters), currentTextBytes)
}
return nil
}
func decodeWaiters(raw string) ([]string, error) {
var decoded any
if err := json.Unmarshal([]byte(raw), &decoded); err != nil {
return nil, err
}
values, ok := decoded.([]any)
if !ok {
return nil, fmt.Errorf("must be an array of strings")
}
waiters := make([]string, len(values))
for i, value := range values {
waiter, ok := value.(string)
if !ok {
return nil, fmt.Errorf("element %d is not a string", i)
}
if err := checkUTF8(currentString{fmt.Sprintf("waiters[%d]", i), waiter}); err != nil {
return nil, err
}
waiters[i] = waiter
}
return waiters, nil
}
func validateIssueVarchars(issue *types.Issue) error {
fields := []currentVarchar{
{"issue id", issue.ID, types.MaxFieldLen},View on GitHub (pinned to 71377f2769)
Solutions
- Convert the legacy waiters value to a JSON array of strings before migration.
- Identify the legacy format and write a converter to the expected array form.
- Correct the malformed row directly in the legacy database.
Example fix
// before (legacy value)
{"waiters": ["alice"]}
// after
["alice"] Defensive patterns
Strategy: type-guard
Validate before calling
var v any; if json.Unmarshal([]byte(raw), &v) != nil || _, ok := v.([]any); !ok { return errors.New("waiters must be a JSON array") } Type guard
func isStringArray(v any) bool { _, ok := v.([]any); return ok } Try / catch
waiters, err := decodeWaiters(raw); if err != nil { log.Warn("unparseable waiters, defaulting to empty", err); waiters = nil } Prevention
- Store waiters only as JSON arrays of strings
- Version the legacy schema and write converters per version
- Never hand-edit JSON columns in the DB
When it happens
Trigger: decodeWaiters receives a raw string that unmarshals to a non-[]any value, e.g. '{"a":1}' or '"foo"'.
Common situations: Waiters stored in an older format (CSV, object map) in the legacy DB, hand-edited rows, or a schema change between legacy versions.
Related errors
- element %d is not a string
- metadata.repo must be a string, got %T
- parsing legacy config: %w
- legacy SQLite issue %s has invalid metadata JSON
- legacy SQLite issue %s metadata: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/5c4097678b1fee46.
Report an issue: GitHub.