{"record":{"id":"0f03b98bd80a773f","repo":"gastownhall/beads","slug":"w-apply-batch-item-d-must-carry-exactly-one-pay","errorCode":null,"errorMessage":"%w: apply batch item %d must carry exactly one payload, got %d","messagePattern":"%w: apply batch item (.+?) must carry exactly one payload, got (.+?)","errorType":"validation","errorClass":"issueops.ErrValidation","httpStatus":null,"severity":"error","filePath":"internal/storage/batch_apply.go","lineNumber":123,"sourceCode":"\treturn plan, nil\n}\n\n// planApplyBatchKeys checks every item's kind/payload agreement and collects\n// the create keys. It runs as its own pass because a ref may name a key\n// declared by a LATER item, which is a different diagnosis from a key nothing\n// declares — and telling them apart means knowing every key before checking any\n// ref.\nfunc planApplyBatchKeys(items []issueops.ApplyItem) (map[string]int, error) {\n\tkeyIndex := make(map[string]int, len(items))\n\tfor i, item := range items {\n\t\tpayloads := 0\n\t\tfor _, present := range []bool{item.Create != nil, item.Update != nil, item.Close != nil, item.DepAdd != nil} {\n\t\t\tif present {\n\t\t\t\tpayloads++\n\t\t\t}\n\t\t}\n\t\tif payloads != 1 {\n\t\t\treturn nil, fmt.Errorf(\"%w: apply batch item %d must carry exactly one payload, got %d\",\n\t\t\t\tissueops.ErrValidation, i, payloads)\n\t\t}\n\t\tvar matches bool\n\t\tswitch item.Kind {\n\t\tcase issueops.ItemCreate:\n\t\t\tmatches = item.Create != nil\n\t\tcase issueops.ItemUpdate:\n\t\t\tmatches = item.Update != nil\n\t\tcase issueops.ItemClose:\n\t\t\tmatches = item.Close != nil\n\t\tcase issueops.ItemDepAdd:\n\t\t\tmatches = item.DepAdd != nil\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"%w: apply batch item %d has unknown kind %q\", issueops.ErrValidation, i, item.Kind)\n\t\t}\n\t\tif !matches {\n\t\t\treturn nil, fmt.Errorf(\"%w: apply batch item %d is kind %q but carries another kind's payload\",\n\t\t\t\tissueops.ErrValidation, i, item.Kind)","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/batch_apply.go#L105-L141","documentation":"Each ApplyBatchRequest item must carry exactly one payload (exactly one of Create, Update, Close, DepAdd non-nil). planApplyBatchKeys counts the non-nil payloads and fails with the item index and actual count, wrapped with issueops.ErrValidation. Zero payloads and multiple payloads are both rejected.","triggerScenarios":"Building an item with no payload set, or with two payloads set (e.g. both Create and Close non-nil), or reusing a single item struct across kinds without clearing other fields.","commonSituations":"Generic builder code that fills multiple payload pointers; copy-pasted item literals; deserialization bugs where JSON fills unexpected payload fields.","solutions":["Ensure each item sets exactly one of Create/Update/Close/DepAdd and leaves the others nil.","If reusing a struct, construct a fresh ApplyBatchItem per operation instead of mutating a shared one.","Match the payload to item.Kind — also see the kind/payload mismatch error that follows this check."],"exampleFix":"// before\nitem := issueops.ApplyBatchItem{Kind: issueops.ItemCreate, Create: cr, Close: cl} // 2 payloads\n// after\nitem := issueops.ApplyBatchItem{Kind: issueops.ItemCreate, Create: cr}","handlingStrategy":"validation","validationCode":"func payloadCount(it issueops.ApplyBatchItem) int {\n    n := 0\n    for _, p := range []*issueops.CreatePayload{it.Create} { if p != nil { n++ } }\n    if it.Update != nil { n++ }\n    if it.Close != nil { n++ }\n    if it.DepAdd != nil { n++ }\n    return n\n}","typeGuard":null,"tryCatchPattern":"_, err := storage.PlanApplyBatch(req)\nif errors.Is(err, issueops.ErrValidation) && strings.Contains(err.Error(), \"exactly one payload\") {\n    return fmt.Errorf(\"malformed batch item: %w\", err)\n}","preventionTips":["Build items via constructors that take one payload and set Kind accordingly.","Never reuse/mutate a shared ApplyBatchItem across operations.","Validate batch items in unit tests with table-driven payloads."],"tags":["validation","batch","payload"],"backgroundTag":"validation-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}