gastownhall/beads · error

existing milestone metadata is not a JSON object

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.

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.

Appendix: 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)