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
- Inspect the wrapped time.ParseError to see the exact bad value.
- Normalize both created_at and updated_at to the same RFC3339 format in the exporter.
- Omit or null the field when unknown so the mapping falls back to time.Now().
- 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
- Use the same timestamp format for created_at and updated_at in exporters.
- Scrub sentinel values ('never', '0', '-') to null before mapping.
- Round-trip test exports through BeadsIssueFromPullIssue to catch format drift.
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
- parse created_at: %w
- unable to parse plugin file: %w
- parse gh output: %w
- record last_import_time: %w
- Notion authentication is not configured. Set notion.token wi
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/47449fe0c96bd755.
Report an issue: GitHub.