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

  1. Inspect the row: sqlite3 legacy.db "SELECT id, created_at, updated_at FROM issues WHERE id='<id>'"
  2. Backfill the missing timestamp with a sensible value (file mtime, max of other rows' timestamps, or created_at as updated_at)
  3. Add a NOT NULL / non-zero check to any tooling that writes to the legacy DB
  4. 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

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


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