gastownhall/beads · error
element %d is not a string
Error message
element %d is not a string
What it means
decodeWaiters requires every element of the waiters JSON array to be a string. If element i is another JSON type (number, object, bool, null), it returns 'element %d is not a string' with the offending index. Each string is then further validated for UTF-8 via checkUTF8.
Source
Thrown at internal/migration/legacysqlite/reader.go:701
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},
{"issue title", issue.Title, currentTitleVarcharRunes},
{"issue status", string(issue.Status), currentShortVarcharRunes},
{"issue type", string(issue.IssueType), currentShortVarcharRunes},
{"issue assignee", issue.Assignee, types.MaxFieldLen},
{"issue created_by", issue.CreatedBy, types.MaxFieldLen},
{"issue owner", issue.Owner, types.MaxFieldLen},View on GitHub (pinned to 71377f2769)
Solutions
- Convert non-string waiter elements to strings in the legacy row.
- Filter out null/non-string entries before migrating.
- Fix the legacy exporter to always emit string waiter identifiers.
Example fix
// before [42, "alice"] // after ["42", "alice"]
Defensive patterns
Strategy: type-guard
Validate before calling
for i, el := range arr { if _, ok := el.(string); !ok { return fmt.Errorf("waiters[%d] is not a string", i) } } Type guard
func allStrings(v any) bool { arr, ok := v.([]any); if !ok { return false }; for _, el := range arr { if _, ok := el.(string); !ok { return false } }; return true } Try / catch
waiters, err := decodeWaiters(raw); if err != nil { log.Warn("bad waiter element, coercing", err); waiters = coerceToStrings(raw) } Prevention
- Enforce string IDs for waiters at write time
- Reject non-string JSON types in the exporter
- Validate legacy arrays element-by-element before migration
When it happens
Trigger: A waiters array like '[1, "alice"]' or '[null]' is passed to decodeWaiters; the first non-string element aborts with its index.
Common situations: Waiter entries stored as IDs/numbers in an older legacy format, nulls from partial exports, or hand-edited JSON.
Related errors
- must be an array of strings
- 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/1e4f8e85d2b6d3b5.
Report an issue: GitHub.