gastownhall/beads · error
legacy SQLite issue %s created_at: %w
Error message
legacy SQLite issue %s created_at: %w
What it means
applyCanonicalTimestamps normalizes legacy created_at values to the canonical current-schema datetime via canonicalCurrentDatetime. This error means the issue's created_at value could not be parsed/normalized — it is not a recognizable timestamp format. Migration refuses to import an issue with an unusable creation timestamp.
Source
Thrown at internal/migration/legacysqlite/reader.go:541
}
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 {
return fmt.Errorf("legacy SQLite issue %s updated_at: %w", issue.ID, err)
}
return nil
}
// checkRequiredScalars enforces the non-empty ID, non-tombstone status, and
// present created_at/updated_at invariants every legacy issue must satisfy.
func checkRequiredScalars(issue *types.Issue) error {
if issue.ID == "" {
return fmt.Errorf("legacy SQLite issue has empty ID")
}
if issue.Status == "tombstone" {
return fmt.Errorf("legacy SQLite issue %s is a tombstone", issue.ID)
}
if issue.CreatedAt.IsZero() || issue.UpdatedAt.IsZero() {
return fmt.Errorf("legacy SQLite issue %s has invalid created_at or updated_at", issue.ID)View on GitHub (pinned to 71377f2769)
Solutions
- Find the row: sqlite3 legacy.db "SELECT id, created_at FROM issues WHERE id='<id>'" and inspect the raw value
- Rewrite created_at to an ISO-8601 / RFC-3339 UTC timestamp the current code accepts, e.g. '2024-01-15T10:30:00Z'
- If the true creation time is unknown, set a best-effort timestamp (e.g. rowid-derived or mtime of the DB file)
- Re-run the migration
Example fix
// before created_at = '01/15/2024 10:30 AM' // after created_at = '2024-01-15T10:30:00Z'
Defensive patterns
Strategy: validation
Validate before calling
from datetime import datetime
def ts_ok(v):
if not v:
return False
try:
datetime.fromisoformat(v.replace('Z', '+00:00'))
return True
except ValueError:
return False
# bad = [r['id'] for r in rows if not ts_ok(r['created_at'])] Type guard
def is_canonical_datetime(v) -> bool:
if not isinstance(v, str) or not v:
return False
try:
datetime.fromisoformat(v.replace('Z', '+00:00'))
return True
except ValueError:
return False Prevention
- Audit timestamp formats across the legacy DB before migrating
- Standardize on ISO-8601 UTC in any tooling that writes issue timestamps
- Dry-run validation on a copy of the DB first
When it happens
Trigger: Legacy row has created_at stored as an empty string, a non-timestamp string, or a format canonicalCurrentDatetime does not accept (e.g. locale-formatted dates, milliseconds-as-string, or NULL coerced oddly).
Common situations: Databases touched by other tools that wrote their own date formats, rows created by very old beads versions with a different timestamp convention, or corrupted rows from a crashed writer.
Related errors
- legacy SQLite issue %s updated_at: %w
- legacy SQLite issue %s has invalid created_at or updated_at
- sealed legacy SQLite database does not match source fingerpr
- sealed legacy SQLite WAL does not match source fingerprint
- legacy SQLite source changed while sealing
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/fb3e41b9ad595d09.
Report an issue: GitHub.