gastownhall/beads · error

comment created_at is zero for issue %s

Error message

comment created_at is zero for issue %s

What it means

Every comment must carry a real creation timestamp; the reader parses created_at and rejects comments whose parsed time is the zero time.Time. bd uses comment CreatedAt for ordering and identity, so zero timestamps cannot be imported.

Source

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

		if err := checkCurrentVarchars(
			currentVarchar{"comment issue_id", issueID, types.MaxFieldLen},
			currentVarchar{"comment author", author, types.MaxFieldLen},
		); err != nil {
			return err
		}
		issue := byID[issueID]
		if issue == nil {
			return fmt.Errorf("orphan comment for %s", issueID)
		}
		if issue.Ephemeral && len(text) > currentTextBytes {
			return fmt.Errorf("legacy SQLite ephemeral comment text is %d bytes (current TEXT maximum %d)", len(text), currentTextBytes)
		}
		created, e := parseTime(at)
		if e != nil {
			return e
		}
		if created.IsZero() {
			return fmt.Errorf("comment created_at is zero for issue %s", issueID)
		}
		identity := commentIdentity{issueID: issueID, author: author, text: text, createdAt: created}
		if priorID, exists := seenComments[identity]; exists {
			return fmt.Errorf("legacy SQLite comments %d and %d share current import identity", priorID, id)
		}
		seenComments[identity] = id
		issue.Comments = append(issue.Comments, &types.Comment{ID: strconv.FormatInt(id, 10), IssueID: issueID, Author: author, Text: text, CreatedAt: created})
	}
	return comments.Err()
}

func validateDependencyGraph(issues []*types.Issue) error {
	scheduling := make(map[string][]string)
	hierarchy := make(map[string][]string)
	var blocking []*types.Dependency
	for _, issue := range issues {
		for _, dep := range issue.Dependencies {
			if dep.IssueID == dep.DependsOnID {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Backfill created_at in the legacy DB: UPDATE comments SET created_at = <iso timestamp> WHERE created_at IS NULL OR created_at = ''
  2. Normalize unparseable timestamps to the expected format (RFC3339) before migrating
  3. If the true time is unrecoverable, use the issue's created_at as a fallback for the affected comments

Example fix

-- before
-- comment created_at NULL
UPDATE comments SET created_at = (SELECT created_at FROM issues WHERE id = comments.issue_id)
WHERE created_at IS NULL OR created_at = '';
-- after: comment inherits its issue's timestamp
Defensive patterns

Strategy: validation

Validate before calling

for (const c of legacyComments) {
  if (!c.created_at || isNaN(Date.parse(c.created_at)))
    throw new Error(`zero/invalid comment created_at on issue ${c.issue_id}`);
}

Type guard

function commentHasCreatedAt(c) {
  return typeof c.created_at === 'string' && c.created_at !== '' && !isNaN(Date.parse(c.created_at));
}

Try / catch

try { migrateLegacySQLite(dbPath) } catch (e) {
  if (e.message.includes('comment created_at is zero')) {
    const issueID = e.message.match(/issue (\S+)/)[1];
    backfillCommentCreatedAt(dbPath, issueID); // fall back to issue created_at
    retry();
  } else throw e;
}

Prevention

When it happens

Trigger: Legacy SQLite comment rows with NULL/empty created_at, or timestamps in a format `parseTime` cannot interpret and which resolve to zero; reported per issue ID.

Common situations: Very old schema versions before created_at existed on comments; rows inserted by scripts that omitted the column; clock/corruption producing unparseable date strings.

Related errors


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