gastownhall/beads · error

updating Linear milestone epic %s: %w

Error message

updating Linear milestone epic %s: %w

What it means

Wraps st.UpdateIssue failures when syncing an already-existing Linear milestone epic in beads. If merged metadata or other fields differ, the epic is updated; a storage error there is wrapped with the epic's ID via %w.

Source

Thrown at cmd/bd/linear.go:620

		if existing.Description != description {
			updates["description"] = description
		}
		if existing.IssueType != types.TypeEpic {
			updates["issue_type"] = string(types.TypeEpic)
		}
		if existing.ExternalRef == nil || strings.TrimSpace(*existing.ExternalRef) != ref {
			updates["external_ref"] = ref
		}
		mergedMetadata, err := mergedLinearMilestoneMetadata(existing.Metadata, ms)
		if err != nil {
			return "", err
		}
		if string(existing.Metadata) != string(mergedMetadata) {
			updates["metadata"] = mergedMetadata
		}
		if len(updates) > 0 {
			if err := st.UpdateIssue(ctx, existing.ID, updates, actor); err != nil {
				return "", fmt.Errorf("updating Linear milestone epic %s: %w", existing.ID, err)
			}
		}
		return ref, nil
	}

	externalRef := ref
	epic := &types.Issue{
		Title:       title,
		Description: description,
		Status:      types.StatusOpen,
		Priority:    2,
		IssueType:   types.TypeEpic,
		ExternalRef: &externalRef,
		Metadata:    metadata,
	}
	if generateID != nil {
		if err := generateID(ctx, epic); err != nil {
			return "", fmt.Errorf("generating Linear milestone epic ID: %w", err)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the wrapped storage error and ensure the database is reachable and not locked
  2. Avoid running multiple Linear syncs concurrently against the same workspace
  3. Re-run the sync; the update is a diff-based metadata merge and safe to retry
Defensive patterns

Strategy: retry

Validate before calling

// pre-check the epic still exists before updating
if _, err := st.GetIssue(ctx, existing.ID); err != nil {
    return "", fmt.Errorf("milestone epic %s vanished: %w", existing.ID, err)
}

Try / catch

err := st.UpdateIssue(ctx, existing.ID, updates, actor)
if err != nil {
    if isTransient(err) { time.Sleep(backoff); return retryUpdate() }
    return "", fmt.Errorf("updating Linear milestone epic %s: %w", existing.ID, err)
}

Prevention

When it happens

Trigger: Linear sync detects changes to a milestone (name/description/metadata) and calls st.UpdateIssue on the existing epic; storage returns an error — concurrent write conflict, locked DB, or the epic was deleted mid-sync.

Common situations: Two sync processes running concurrently on the same workspace; Dolt database unavailable; epic issue deleted by a user while a sync was in flight.

Related errors


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