{"record":{"id":"8a6f9c587659d9bf","repo":"gastownhall/beads","slug":"existing-milestone-metadata-is-not-a-json-object","errorCode":null,"errorMessage":"existing milestone metadata is not a JSON object: %w","messagePattern":"existing milestone metadata is not a JSON object: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/linear.go","lineNumber":685,"sourceCode":"\t\t}\n\t\tref := \"\"\n\t\tif issue.ExternalRef != nil {\n\t\t\tref = strings.TrimSpace(*issue.ExternalRef)\n\t\t}\n\t\tif ref == \"\" {\n\t\t\treturn issue, nil\n\t\t}\n\t}\n\treturn nil, nil\n}\n\nfunc mergedLinearMilestoneMetadata(existing json.RawMessage, ms *linear.ProjectMilestone) (json.RawMessage, error) {\n\tdata := make(map[string]interface{})\n\tif len(existing) > 0 {\n\t\ttrimmed := strings.TrimSpace(string(existing))\n\t\tif trimmed != \"\" && trimmed != \"null\" {\n\t\t\tif err := json.Unmarshal(existing, &data); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"existing milestone metadata is not a JSON object: %w\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\tlinearMeta, _ := data[\"linear\"].(map[string]interface{})\n\tif linearMeta == nil {\n\t\tlinearMeta = make(map[string]interface{})\n\t}\n\tlinearMeta[\"kind\"] = \"project_milestone\"\n\tlinearMeta[\"project_milestone\"] = map[string]interface{}{\n\t\t\"id\":          strings.TrimSpace(ms.ID),\n\t\t\"name\":        ms.Name,\n\t\t\"description\": ms.Description,\n\t\t\"progress\":    ms.Progress,\n\t\t\"targetDate\":  ms.TargetDate,\n\t}\n\tdata[\"linear\"] = linearMeta\n","sourceCodeStart":667,"sourceCodeEnd":703,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/linear.go#L667-L703","documentation":"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.","triggerScenarios":"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\":{...}}`.","commonSituations":"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.","solutions":["Inspect the issue metadata (bd show or query the metadata column) and fix it to be a JSON object, e.g. `{}` or `{\"linear\":{...}}`","If the metadata is garbage, reset it to an empty object or delete the issue's metadata so merging starts fresh","Re-run bd linear pull/sync after correcting the metadata"],"exampleFix":"// before (metadata column)\n[\"legacy\",\"format\"]\n// after\n{\"linear\":{\"milestoneId\":\"...\"}}","handlingStrategy":"validation","validationCode":"func isJSONObject(raw json.RawMessage) bool {\n\tt := strings.TrimSpace(string(raw))\n\tif t == \"\" || t == \"null\" {\n\t\treturn true\n\t}\n\tvar m map[string]interface{}\n\treturn json.Unmarshal(raw, &m) == nil\n}","typeGuard":"func asMetadataObject(raw json.RawMessage) (map[string]interface{}, bool) {\n\tvar m map[string]interface{}\n\tif err := json.Unmarshal(raw, &m); err != nil || m == nil {\n\t\treturn nil, false\n\t}\n\treturn m, true\n}","tryCatchPattern":"merged, err := mergedLinearMilestoneMetadata(existing, ms)\nif err != nil {\n\tvar syncErr = fmt.Errorf(\"milestone sync skipped for %s: %w\", issue.ID, err)\n\tlog.Warn(syncErr)\n\t// reset metadata to {} and retry once\n}","preventionTips":["Only write JSON objects into issue metadata; never arrays or bare strings","Add a schema check in any tool that writes bd metadata","Spot-check metadata after imports/migrations"],"tags":["json","metadata","linear","sync"],"backgroundTag":"invalid-json-metadata","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}