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.

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)

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.