gastownhall/beads · error
legacy SQLite issue %s has invalid created_at or updated_at
Error message
legacy SQLite issue %s has invalid created_at or updated_at
What it means
checkRequiredScalars requires both created_at and updated_at to be present (non-zero) on every legacy issue. This error means the row carried a zero/missing timestamp even though it parsed successfully — the value was absent or epoch-zero rather than malformed.
Source
Thrown at internal/migration/legacysqlite/reader.go:559
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
}
// checkRemovedFields rejects legacy rows that populate columns the current
// schema no longer supports, then validates the tri-state boolean columns.
func (x legacyExtras) checkRemovedFields(issue *types.Issue) error {
if nonempty(x.closedBy, x.deletedBy, x.deleteReason, x.originalType, x.hookBead, x.roleBead, x.agentState, x.lastActivity, x.roleType, x.rig) || x.deletedAt.Valid || x.crystallizes.Int64 != 0 || x.quality.Valid || (issue.SourceRepo != "" && issue.SourceRepo != ".") {
return fmt.Errorf("legacy SQLite issue %s uses unsupported removed fields", issue.ID)
}
for _, b := range []struct {
name string
v sql.NullInt64
}{{"ephemeral", x.ephemeral}, {"pinned", x.pinned}, {"is_template", x.template}} {
if b.v.Valid && b.v.Int64 != 0 && b.v.Int64 != 1 {
return fmt.Errorf("issue %s has invalid %s boolean", issue.ID, b.name)
}
}View on GitHub (pinned to 71377f2769)
Solutions
- Inspect the row: sqlite3 legacy.db "SELECT id, created_at, updated_at FROM issues WHERE id='<id>'"
- Backfill the missing timestamp with a sensible value (file mtime, max of other rows' timestamps, or created_at as updated_at)
- Add a NOT NULL / non-zero check to any tooling that writes to the legacy DB
- Re-run the migration
Example fix
// before created_at = NULL, updated_at = NULL // after UPDATE issues SET created_at='2024-01-01T00:00:00Z', updated_at='2024-01-01T00:00:00Z' WHERE id='<id>';
Defensive patterns
Strategy: validation
Validate before calling
-- run before migration SELECT id FROM issues WHERE created_at IS NULL OR created_at = '' OR updated_at IS NULL OR updated_at = '';
Type guard
def has_timestamps(row) -> bool:
return bool(row.get('created_at')) and bool(row.get('updated_at')) Prevention
- Reject NULL/empty timestamps at write time in custom tooling
- Backfill before migration rather than after
- Prefer created_at as a safe fallback for missing updated_at
When it happens
Trigger: Legacy row has created_at or updated_at stored as NULL, empty, or a value that parses to time.Time zero (e.g. '0000-00-00' style placeholders).
Common situations: Rows inserted by scripts that omitted timestamps; imports from trackers that did not track update times; pre-dated rows from very early schema versions.
Understand the failure class
Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.
Related errors
- issue %s waiters: %w
- legacy SQLite issue %s created_at: %w
- legacy SQLite issue %s updated_at: %w
- legacy SQLite issue has empty ID
- orphan label for %s
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/cb7ad5e9ea0ad8a2.
Report an issue: GitHub.