gastownhall/beads · error

existing milestone metadata is not a JSON object: %w

Error message

existing milestone metadata is not a JSON object: %w

What it means

mergedLinearMilestoneMetadata merges existing issue metadata JSON with fresh Linear milestone data before syncing to Linear. It requires the stored metadata to be a JSON object (unmarshalable into map[string]interface{}); if the existing raw metadata is any other JSON value (array, string, number), json.Unmarshal into the map fails and this error wraps the underlying parse error.

Source

Thrown at cmd/bd/linear.go:685

		}
		ref := ""
		if issue.ExternalRef != nil {
			ref = strings.TrimSpace(*issue.ExternalRef)
		}
		if ref == "" {
			return issue, nil
		}
	}
	return nil, nil
}

func mergedLinearMilestoneMetadata(existing json.RawMessage, ms *linear.ProjectMilestone) (json.RawMessage, error) {
	data := make(map[string]interface{})
	if len(existing) > 0 {
		trimmed := strings.TrimSpace(string(existing))
		if trimmed != "" && trimmed != "null" {
			if err := json.Unmarshal(existing, &data); err != nil {
				return nil, fmt.Errorf("existing milestone metadata is not a JSON object: %w", err)
			}
		}
	}

	linearMeta, _ := data["linear"].(map[string]interface{})
	if linearMeta == nil {
		linearMeta = make(map[string]interface{})
	}
	linearMeta["kind"] = "project_milestone"
	linearMeta["project_milestone"] = map[string]interface{}{
		"id":          strings.TrimSpace(ms.ID),
		"name":        ms.Name,
		"description": ms.Description,
		"progress":    ms.Progress,
		"targetDate":  ms.TargetDate,
	}
	data["linear"] = linearMeta

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the issue metadata (bd show or query the metadata column) and fix it to be a JSON object, e.g. `{}` or `{"linear":{...}}`
  2. If the metadata is garbage, reset it to an empty object or delete the issue's metadata so merging starts fresh
  3. Re-run bd linear pull/sync after correcting the metadata

Example fix

// before (metadata column)
["legacy","format"]
// after
{"linear":{"milestoneId":"..."}}
Defensive patterns

Strategy: validation

Validate before calling

func isJSONObject(raw json.RawMessage) bool {
	t := strings.TrimSpace(string(raw))
	if t == "" || t == "null" {
		return true
	}
	var m map[string]interface{}
	return json.Unmarshal(raw, &m) == nil
}

Type guard

func asMetadataObject(raw json.RawMessage) (map[string]interface{}, bool) {
	var m map[string]interface{}
	if err := json.Unmarshal(raw, &m); err != nil || m == nil {
		return nil, false
	}
	return m, true
}

Try / catch

merged, err := mergedLinearMilestoneMetadata(existing, ms)
if err != nil {
	var syncErr = fmt.Errorf("milestone sync skipped for %s: %w", issue.ID, err)
	log.Warn(syncErr)
	// reset metadata to {} and retry once
}

Prevention

When it happens

Trigger: Calling ensureLinearMilestoneEpic when the issue's metadata column contains a non-object JSON value (e.g. `[1,2]`, `"text"`, or `42`) rather than an object like `{"linear":{...}}`.

Common situations: Metadata was hand-edited or written by an older/buggy bd version or external tool that stored a JSON array/string instead of an object; a migration or import wrote malformed metadata into the issue record.

Related errors


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