{"record":{"id":"497369fd71fe7c84","repo":"gastownhall/beads","slug":"w-apply-batch-requires-an-actor","errorCode":null,"errorMessage":"%w: apply batch requires an actor","messagePattern":"%w: apply batch requires an actor","errorType":"validation","errorClass":"issueops.ErrValidation","httpStatus":null,"severity":"error","filePath":"internal/storage/batch_apply.go","lineNumber":71,"sourceCode":"\tItems []issueops.ApplyItem\n\t// KeyIndex maps each create item's Key to the index that declares it. The\n\t// body resolves refs through it and the wire half reports an unknown key\n\t// from it, so the two cannot disagree about what a key means.\n\tKeyIndex map[string]int\n}\n\n// PlanApplyBatch validates an apply-batch request and normalizes its waits-for\n// gate metadata. It is the whole of the role's request validation: every\n// implementation calls it before touching a substrate, so a refused request\n// costs no database work anywhere.\n//\n// THE ORDER OF THE CHECKS IS PART OF THE CONTRACT, because a request can be\n// wrong in several ways at once and a caller fixing them one at a time needs\n// the same answer every time. Request-level shape first, then per-item shape,\n// then the ref graph, then the guards.\nfunc PlanApplyBatch(in issueops.ApplyBatchRequest) (ApplyBatchPlan, error) {\n\tif in.Actor == \"\" {\n\t\treturn ApplyBatchPlan{}, fmt.Errorf(\"%w: apply batch requires an actor\", issueops.ErrValidation)\n\t}\n\tif len(in.Items) == 0 {\n\t\treturn ApplyBatchPlan{}, fmt.Errorf(\"%w: apply batch requires at least one item\", issueops.ErrValidation)\n\t}\n\tif len(in.Items) > issueops.MaxApplyBatchItems {\n\t\treturn ApplyBatchPlan{}, fmt.Errorf(\"%w: apply batch accepts at most %d items, got %d\",\n\t\t\tissueops.ErrValidation, issueops.MaxApplyBatchItems, len(in.Items))\n\t}\n\n\tkeyIndex, err := planApplyBatchKeys(in.Items)\n\tif err != nil {\n\t\treturn ApplyBatchPlan{}, err\n\t}\n\n\tplan := ApplyBatchPlan{\n\t\tActor:                 in.Actor,\n\t\tProvenance:            in.Provenance,\n\t\tForceIDPrefix:         in.ForceIDPrefix,","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/batch_apply.go#L53-L89","documentation":"PlanApplyBatch requires a non-empty Actor because every planned mutation must be attributable for audit/history. An empty Actor is the first request-shape check and fails before any item validation, wrapped with issueops.ErrValidation so callers can errors.Is on it.","triggerScenarios":"Calling PlanApplyBatch with issueops.ApplyBatchRequest{Actor: \"\"} — e.g. agent/CLI code that never set the acting user, or a struct literal omitting the Actor field.","commonSituations":"Headless scripts or bots constructing the request programmatically and forgetting the actor; refactors that renamed/dropped the Actor field; tests reusing a zero-value request struct.","solutions":["Set Actor to the acting user/bot identity before calling PlanApplyBatch.","If the caller is a service, propagate the authenticated identity into the request instead of leaving it zero.","Handle the error with errors.Is(err, issueops.ErrValidation) to distinguish validation failures from storage errors."],"exampleFix":"// before\nplan, err := PlanApplyBatch(issueops.ApplyBatchRequest{Items: items})\n// after\nplan, err := PlanApplyBatch(issueops.ApplyBatchRequest{Actor: \"agent:ci-bot\", Items: items})","handlingStrategy":"validation","validationCode":"if req.Actor == \"\" {\n    return fmt.Errorf(\"actor is required for batch apply\")\n}","typeGuard":null,"tryCatchPattern":"_, err := storage.PlanApplyBatch(req)\nif errors.Is(err, issueops.ErrValidation) {\n    return fmt.Errorf(\"invalid batch request: %w\", err)\n}","preventionTips":["Propagate the authenticated identity into every request struct.","Avoid zero-value ApplyBatchRequest literals; use a constructor that requires actor.","Add a lint/test asserting Actor is set in all batch call sites."],"tags":["validation","batch","actor"],"backgroundTag":"validation-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}