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

  1. Find the row: sqlite3 legacy.db "SELECT id, created_at FROM issues WHERE id='<id>'" and inspect the raw value
  2. Rewrite created_at to an ISO-8601 / RFC-3339 UTC timestamp the current code accepts, e.g. '2024-01-15T10:30:00Z'
  3. If the true creation time is unknown, set a best-effort timestamp (e.g. rowid-derived or mtime of the DB file)
  4. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/fb3e41b9ad595d09. Report an issue: GitHub.