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
- Backfill created_at in the legacy DB: UPDATE comments SET created_at = <iso timestamp> WHERE created_at IS NULL OR created_at = ''
- Normalize unparseable timestamps to the expected format (RFC3339) before migrating
- 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
- Backfill NULL created_at on comments before import (issue timestamp is a good fallback)
- Enforce NOT NULL on comments.created_at
- Normalize timestamp formats to RFC3339 pre-migration
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
- timestamp %q: %w
- invalid timestamp %q
- timestamp rounds outside current DATETIME range
- %s: %w
- dependency created_at is zero for %s -> %s
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/fad8ac8a47b318f5.
Report an issue: GitHub.