gastownhall/beads · error

Linear project milestone is missing id

Error message

Linear project milestone is missing id

What it means

ensureLinearMilestoneEpic validates that the incoming linear.ProjectMilestone has a non-empty ID before creating or updating its epic representation in beads. A milestone without an ID cannot be referenced or deduplicated, so the function fails fast with this error.

Solutions

  1. Verify the Linear API response actually includes milestone IDs (check API scopes/token and endpoint version)
  2. Skip or log-and-continue for milestones lacking IDs in your sync script before calling the conversion path
  3. Refresh/re-fetch milestone data from Linear and re-run the import

Example fix

// before
ensureLinearMilestoneEpic(ctx, st, &linear.ProjectMilestone{Name: "v1"}, ...)
// after
if strings.TrimSpace(ms.ID) == "" { return } // skip; then
ensureLinearMilestoneEpic(ctx, st, ms, ...)
Defensive patterns

Strategy: validation

Validate before calling

// guard before calling the sync conversion
if ms == nil || strings.TrimSpace(ms.ID) == "" {
    return fmt.Errorf("skipping milestone with empty ID")
}

Type guard

func validMilestone(ms *linear.ProjectMilestone) bool {
    return ms != nil && strings.TrimSpace(ms.ID) != ""
}

Prevention

When it happens

Trigger: Linear sync encounters a ProjectMilestone object whose ID field is empty or whitespace-only — typically from a malformed API response or hand-crafted webhook payload passed into the conversion pipeline.

Common situations: Linear API returning partial data (deprecated endpoint, scope changes); custom import scripts constructing milestone objects manually; upstream Linear schema changes after a library update.

Related errors


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

Appendix: source

Thrown at cmd/bd/linear.go:579

	}

	return hooks
}

const linearMilestoneExternalRefPrefix = "linear:project-milestone:"

func linearMilestoneExternalRef(id string) string {
	return linearMilestoneExternalRefPrefix + strings.TrimSpace(id)
}

func isLinearMilestoneExternalRef(ref string) bool {
	return strings.HasPrefix(strings.TrimSpace(ref), linearMilestoneExternalRefPrefix)
}

func ensureLinearMilestoneEpic(ctx context.Context, st storage.Storage, ms *linear.ProjectMilestone, actor string, generateID func(context.Context, *types.Issue) error) (string, error) {
	milestoneID := strings.TrimSpace(ms.ID)
	if milestoneID == "" {
		return "", fmt.Errorf("Linear project milestone is missing id")
	}
	title := strings.TrimSpace(ms.Name)
	if title == "" {
		title = milestoneID
	}
	description := ms.Description
	ref := linearMilestoneExternalRef(milestoneID)

	metadata, err := mergedLinearMilestoneMetadata(nil, ms)
	if err != nil {
		return "", err
	}

	existing, err := findLinearMilestoneEpic(ctx, st, ref, milestoneID, title)
	if err != nil {
		return "", err
	}
	if existing != nil {

View on GitHub (pinned to 71377f2769)