{"record":{"id":"98cc6a625bb5d239","repo":"plandex-ai/plandex","slug":"error-marshalling-plan-apply-v","errorCode":null,"errorMessage":"error marshalling plan apply: %v","messagePattern":"error marshalling plan apply: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/db/result_helpers.go","lineNumber":746,"sourceCode":"\tvar descriptionIds []string\n\tvar messageIds []string\n\n\tfor _, result := range pendingDbResults {\n\t\tresultIds = append(resultIds, result.Id)\n\t}\n\tfor _, desc := range convoMessageDescriptions {\n\t\tdescriptionIds = append(descriptionIds, desc.Id)\n\t\tmessageIds = append(messageIds, desc.ConvoMessageId)\n\t}\n\n\tplanApply.PlanFileResultIds = resultIds\n\tplanApply.ConvoMessageDescriptionIds = descriptionIds\n\tplanApply.ConvoMessageIds = messageIds\n\n\t// Store the PlanApply object\n\tbytes, err := json.MarshalIndent(planApply, \"\", \"  \")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error marshalling plan apply: %v\", err)\n\t}\n\n\tappliesDir := getPlanAppliesDir(orgId, planId)\n\terr = os.MkdirAll(appliesDir, 0755)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error creating applies dir: %v\", err)\n\t}\n\n\terr = os.WriteFile(filepath.Join(appliesDir, planApply.Id+\".json\"), bytes, 0644)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error writing plan apply file: %v\", err)\n\t}\n\n\tmsg := \"✅ Marked pending results as applied\"\n\n\tcurrentFiles := currentPlanState.CurrentPlanFiles.Files\n\tvar sortedFiles []string\n\tfor path := range currentFiles {","sourceCodeStart":728,"sourceCodeEnd":764,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/result_helpers.go#L728-L764","documentation":"After all apply goroutines succeed, ApplyPlan builds a PlanApply record and serializes it with json.MarshalIndent before writing it to the plan's applies directory. If marshalling fails, \"error marshalling plan apply: %v\" is returned. In practice this is rare — PlanApply is a plain struct of strings, UUIDs, and timestamps — and almost always indicates an impossible-to-marshal field was added to the struct (e.g., a channel, func, or unsupported custom type) rather than bad caller input.","triggerScenarios":"Calling ApplyPlan (any params) when the PlanApply struct contains a field json.MarshalIndent cannot encode: an unsupported type added in a code/schema change, a custom MarshalJSON that errors, or cyclic/non-serializable data placed into the record.","commonSituations":"A recent code change added a new field to PlanApply with a non-serializable type; a custom type's MarshalJSON implementation panics or errors; embedding of an unserializable runtime object into the record after a merge.","solutions":["Inspect the wrapped json error to identify the offending field type in the PlanApply struct.","Fix the struct: remove or replace the non-serializable field (channels, funcs, cycles) with a serializable representation.","If a custom MarshalJSON on PlanApply or a nested type is failing, correct or remove it.","Note the apply side-effects may already be persisted (results, descriptions, contexts) but no PlanApply record exists; after fixing, re-run apply or write the record manually if appropriate.","Pin/rollback the recent version change that introduced the new field if an immediate hotfix is not possible."],"exampleFix":"// before: non-serializable field added to PlanApply\ntype PlanApply struct {\n    Id      string\n    Results map[string]*PlanFileResult\n    onChange func() // json: cannot marshal func\n}\n\n// after: keep serializable data only\ntype PlanApply struct {\n    Id      string\n    Results map[string]*PlanFileResult\n    // callbacks live outside the persisted record\n}","handlingStrategy":"try-catch","validationCode":"// fail fast on struct changes: verify PlanApply marshals before invoking apply\nif _, err := json.Marshal(&db.PlanApply{Id: \"smoke\", OrgId: orgId, PlanId: planId, UserId: userId, CreatedAt: time.Now()}); err != nil {\n    return fmt.Errorf(\"PlanApply struct not serializable: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"err := db.ApplyPlan(ctx, params)\nif err != nil && strings.Contains(err.Error(), \"error marshalling plan apply:\") {\n    // code/schema issue: fix PlanApply struct, not caller input\n    return fmt.Errorf(\"record serialization bug, apply side-effects may be partial: %w\", err)\n}","preventionTips":["Add a CI test that marshals a fully-populated PlanApply to catch non-serializable fields at build time.","Keep PlanApply limited to JSON-safe types (strings, UUIDs, timestamps, string slices).","Run go vet / marshalling smoke tests after any struct change.","If this fires, check whether a recent merge added a field to PlanApply and roll back or fix it."],"tags":["go","json-marshalling","plan-apply","serialization"],"backgroundTag":"json-marshal-failed","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}