gastownhall/beads · error

parse updated_at: %w

Error message

parse updated_at: %w

What it means

Same class as the created_at failure but for the UpdatedAt field: parseMappingTimestamp rejected the string. Happens after created_at parsed successfully, so the issue is specific to the updated_at value. An empty/absent value is fine (hasUpdated=false, falls back to now), only a non-empty unparseable string errors.

Source

Thrown at internal/notion/mapping.go:91

	if config == nil {
		config = DefaultMappingConfig()
	}

	status := statusToBeads(issue.Status, config)
	priority := priorityToBeads(issue.Priority, config)
	issueTypeRaw := issue.IssueType
	if strings.TrimSpace(issueTypeRaw) == "" {
		issueTypeRaw = issue.Type
	}
	issueType := typeToBeads(issueTypeRaw, config)

	createdAt, hasCreated, err := parseMappingTimestamp(string(issue.CreatedAt))
	if err != nil {
		return nil, fmt.Errorf("parse created_at: %w", err)
	}
	updatedAt, hasUpdated, err := parseMappingTimestamp(string(issue.UpdatedAt))
	if err != nil {
		return nil, fmt.Errorf("parse updated_at: %w", err)
	}
	now := time.Now().UTC()
	if !hasCreated {
		createdAt = now
	}
	if !hasUpdated {
		updatedAt = createdAt
	}

	beadsIssue := &types.Issue{
		ID:           strings.TrimSpace(issue.ID),
		Title:        strings.TrimSpace(issue.Title),
		Description:  strings.TrimSpace(issue.Description),
		Status:       status,
		Priority:     priority,
		IssueType:    issueType,
		Assignee:     issue.Assignee,
		Labels:       append([]string(nil), issue.Labels...),

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped time.ParseError to see the exact bad value.
  2. Normalize both created_at and updated_at to the same RFC3339 format in the exporter.
  3. Omit or null the field when unknown so the mapping falls back to time.Now().
  4. Add a preprocessing step that validates timestamps with time.Parse before calling BeadsIssueFromPullIssue.

Example fix

// before
issue.UpdatedAt = "never" // unparseable
bead, err := BeadsIssueFromPullIssue(issue, config) // parse updated_at error

// after
if updated, err := time.Parse(time.RFC3339, raw); err == nil {
    issue.UpdatedAt = types.String(updated.UTC().Format(time.RFC3339))
} else {
    issue.UpdatedAt = types.String("") // falls back to now
}
Defensive patterns

Strategy: validation

Validate before calling

if _, err := time.Parse(time.RFC3339, updatedAt); err != nil {
    return fmt.Errorf("invalid updated_at %q: %w", updatedAt, err)
}

Try / catch

if err != nil && strings.Contains(err.Error(), "parse updated_at") {
    log.Printf("bad updated_at value from source: %v", err)
}

Prevention

When it happens

Trigger: UpdatedAt formatted differently from CreatedAt in the source data (mixed exporters); sentinel strings like 'never' or 'n/a' in the updated field; epoch numbers as strings; timestamps with fractional seconds beyond the accepted layouts.

Common situations: Different tools writing created vs updated fields with different precision; hand-edited JSON exports; migrations from another tracker leaving '0' or '-' in updated_at; Notion last_edited_time exported with a layout the mapping doesn't expect.

Related errors


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