gastownhall/beads · error

legacy SQLite issue %s updated_at: %w

Error message

legacy SQLite issue %s updated_at: %w

What it means

Companion to the created_at normalization error: applyCanonicalTimestamps failed to normalize the issue's updated_at value to the canonical current-schema datetime. Migration will not import an issue whose last-updated timestamp cannot be parsed, since updated_at is required by the current schema.

Source

Thrown at internal/migration/legacysqlite/reader.go:544

		}
		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)
	}
	return nil
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the raw value: sqlite3 legacy.db "SELECT id, updated_at FROM issues WHERE id='<id>'"
  2. Rewrite updated_at as a valid ISO-8601 UTC timestamp, e.g. '2024-06-01T12:00:00Z'
  3. Use created_at as a fallback value if the true update time is unrecoverable
  4. Re-run the migration

Example fix

// before
updated_at = '1717243200000'
// after
updated_at = '2024-06-01T12:00: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['updated_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's updated_at is empty, NULL-derived, or in a format canonicalCurrentDatetime cannot parse (non-ISO string, integer epoch in unexpected units, etc.).

Common situations: Third-party edits to the legacy DB, very old schema versions with different timestamp encoding, or partial writes from a crashed process.

Related errors


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