{"record":{"id":"ed671c557f89effc","repo":"gastownhall/beads","slug":"merge-slot-acquire-holder-must-not-be-empty","errorCode":null,"errorMessage":"merge-slot acquire: holder must not be empty","messagePattern":"merge-slot acquire: holder must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/merge_slot.go","lineNumber":85,"sourceCode":"\tif err != nil || slot == nil {\n\t\treturn nil, fmt.Errorf(\"merge slot not found: %s (run 'bd merge-slot create' first): %w\",\n\t\t\tslotID, ErrNotFound)\n\t}\n\tmeta := parseSlotMeta(slot)\n\treturn &MergeSlotStatus{\n\t\tSlotID:    slotID,\n\t\tAvailable: slot.Status == types.StatusOpen,\n\t\tHolder:    meta.Holder,\n\t\tWaiters:   meta.Waiters,\n\t}, nil\n}\n\n// MergeSlotAcquireImpl is the shared implementation of Storage.MergeSlotAcquire.\n// It uses RunInTransaction to ensure atomic check-and-set, preventing two\n// agents from simultaneously acquiring the slot.\nfunc MergeSlotAcquireImpl(ctx context.Context, s Storage, holder, actor string, wait bool) (*MergeSlotResult, error) {\n\tif holder == \"\" {\n\t\treturn nil, fmt.Errorf(\"merge-slot acquire: holder must not be empty\")\n\t}\n\n\tslotID := MergeSlotID(ctx, s)\n\tvar result MergeSlotResult\n\tresult.SlotID = slotID\n\n\terr := s.RunInTransaction(ctx,\n\t\tfmt.Sprintf(\"bd: acquire merge slot %s for %s\", slotID, holder),\n\t\tfunc(tx Transaction) error {\n\t\t\tslot, err := tx.GetIssue(ctx, slotID)\n\t\t\tif err != nil || slot == nil {\n\t\t\t\treturn fmt.Errorf(\"merge slot not found: %s (run 'bd merge-slot create' first)\", slotID)\n\t\t\t}\n\n\t\t\tmeta := parseSlotMeta(slot)\n\t\t\tresult.Holder = meta.Holder\n\n\t\t\tif slot.Status != types.StatusOpen {","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/merge_slot.go#L67-L103","documentation":"MergeSlotAcquireImpl requires a non-empty holder identity because the slot's whole purpose is recording WHO holds the exclusive merge lock. An empty holder would make the slot un-releasable and audit-invisible, so it is rejected immediately with this static validation error before any transaction runs.","triggerScenarios":"Calling MergeSlotAcquire (bd merge-slot acquire) with an empty holder string — e.g. an unset agent-name variable, a CLI flag omitted, or an environment variable like $HOSTNAME/$AGENT_ID empty in the calling shell or CI job.","commonSituations":"CI pipeline where an agent identity env var was never exported; script interpolating an unset variable into --holder \"\"; misconfigured agent bootstrap that skips identity assignment.","solutions":["Pass a non-empty holder identifier (agent name, hostname, session ID) to MergeSlotAcquire / the CLI flag","In scripts, default or validate the identity: holder=\"${AGENT_NAME:-$(hostname)}\" and fail fast if still empty","Fix the CI/bootstrap configuration so the agent identity env var is exported before the acquire call"],"exampleFix":"// before\nholder := os.Getenv(\"AGENT_NAME\") // empty in CI\nres, err := store.MergeSlotAcquire(ctx, holder, actor, true)\n// after\nholder := os.Getenv(\"AGENT_NAME\")\nif holder == \"\" { holder, _ = os.Hostname() }\nif holder == \"\" { return errors.New(\"cannot acquire merge slot: no agent identity\") }\nres, err := store.MergeSlotAcquire(ctx, holder, actor, true)","handlingStrategy":"validation","validationCode":"if holder == \"\" {\n\treturn errors.New(\"merge-slot acquire: holder must not be empty\")\n}\n// then call the API","typeGuard":"func validHolder(holder string) bool { return strings.TrimSpace(holder) != \"\" }","tryCatchPattern":null,"preventionTips":["Validate holder identity at CLI/entrypoint before calling storage","Default to os.Hostname() or agent session ID when identity env vars are unset","Fail fast in CI if AGENT_NAME/identity variables are empty","Trim whitespace — a blank-string holder is as bad as empty"],"tags":["merge-queue","validation","missing-argument"],"backgroundTag":"missing-required-argument","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}