gastownhall/beads · error

invalid timestamp %q

Error message

invalid timestamp %q

What it means

parseTime could not parse the timestamp string with any of its three accepted layouts: RFC3339Nano, '2006-01-02 15:04:05.999999999-07:00', or '2006-01-02 15:04:05.999999999'. The reader refuses to import rows with unparseable datetimes so the current database never receives malformed DATETIME values.

Source

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

	return false
}
func nullString(v sql.NullString) string {
	if v.Valid {
		return v.String
	}
	return ""
}
func parseTime(s string) (time.Time, error) {
	for _, layout := range []string{time.RFC3339Nano, "2006-01-02 15:04:05.999999999-07:00", "2006-01-02 15:04:05.999999999"} {
		if t, e := time.Parse(layout, s); e == nil {
			canonical, err := canonicalCurrentDatetime(t)
			if err != nil {
				return time.Time{}, fmt.Errorf("timestamp %q: %w", s, err)
			}
			return canonical, nil
		}
	}
	return time.Time{}, fmt.Errorf("invalid timestamp %q", s)
}

func canonicalCurrentDatetime(t time.Time) (time.Time, error) {
	canonical := t.UTC().Round(time.Second)
	if year := canonical.Year(); year < 0 || year > 9999 {
		return time.Time{}, fmt.Errorf("timestamp rounds outside current DATETIME range")
	}
	return canonical, nil
}

func parseOptionalTime(name string, raw sql.NullString) (*time.Time, error) {
	if !raw.Valid {
		return nil, nil
	}
	parsed, err := parseTime(raw.String)
	if err != nil {
		return nil, fmt.Errorf("%s: %w", name, err)
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Locate the row whose timestamp matches %q in the message and inspect the raw column value.
  2. Rewrite the value to RFC3339 (e.g. '2024-05-01T12:00:00Z') or 'YYYY-MM-DD HH:MM:SS' in the legacy SQLite DB.
  3. If the value is a Unix epoch number, convert it: UPDATE t SET ts = strftime('%Y-%m-%d %H:%M:%S', ts, 'unixepoch').
  4. Scan the whole legacy DB for non-conforming timestamps before migrating (regex pre-pass over datetime columns).

Example fix

// before: created_at = '1714560000' (unix epoch)
UPDATE dependencies SET created_at = strftime('%Y-%m-%d %H:%M:%S', created_at, 'unixepoch') WHERE created_at GLOB '[0-9]*' AND created_at NOT GLOB '*-*';
// after: parseable datetime text
Defensive patterns

Strategy: validation

Validate before calling

func parseableTimestamp(s string) bool {
    for _, layout := range []string{time.RFC3339Nano, "2006-01-02 15:04:05.999999999-07:00", "2006-01-02 15:04:05.999999999"} {
        if _, err := time.Parse(layout, s); err == nil {
            return true
        }
    }
    return false
}
// scan created_at/updated_at/closed_at columns with this before migrating

Try / catch

if err := migrateLegacy(db); err != nil {
    if strings.Contains(err.Error(), "invalid timestamp") {
        // extract %q value from message, fix that row, retry
        return fmt.Errorf("normalize timestamps in legacy DB and retry: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: A legacy SQLite row's created_at/updated_at/closed_at column contains text in an unsupported format — e.g. Unix epoch integers, 'YYYY/MM/DD' slashes, local formats like '01/02/2020 3pm', or empty-but-non-null strings — passed via parseOptionalTime, appendLegacyDependencyRow, or loadComments.

Common situations: Very old bd versions writing epoch integers instead of text timestamps; third-party SQLite editors inserting locale-formatted dates; CAST artifacts producing formats with unexpected timezone suffixes; test databases seeded with fake date strings.

Related errors


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