{"record":{"id":"6e1d872dd9c338da","repo":"gastownhall/beads","slug":"slot-held-by-s-not-s","errorCode":null,"errorMessage":"slot held by %s, not %s","messagePattern":"slot held by (.+?), not (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/merge_slot.go","lineNumber":167,"sourceCode":"\treturn &result, nil\n}\n\n// MergeSlotReleaseImpl is the shared implementation of Storage.MergeSlotRelease.\nfunc MergeSlotReleaseImpl(ctx context.Context, s Storage, holder, actor string) error {\n\tslotID := MergeSlotID(ctx, s)\n\n\treturn s.RunInTransaction(ctx,\n\t\tfmt.Sprintf(\"bd: release merge slot %s\", slotID),\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\", slotID)\n\t\t\t}\n\n\t\t\tmeta := parseSlotMeta(slot)\n\n\t\t\tif holder != \"\" && meta.Holder != holder {\n\t\t\t\treturn fmt.Errorf(\"slot held by %s, not %s\", meta.Holder, holder)\n\t\t\t}\n\n\t\t\tif slot.Status == types.StatusOpen {\n\t\t\t\t// Already released; idempotent.\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tnewMeta := slotMeta{Waiters: meta.Waiters}\n\t\t\tmetaStr, err := encodeSlotMeta(newMeta)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to encode slot metadata: %w\", err)\n\t\t\t}\n\t\t\treturn tx.UpdateIssue(ctx, slot.ID, map[string]interface{}{\n\t\t\t\t\"status\":   types.StatusOpen,\n\t\t\t\t\"metadata\": metaStr,\n\t\t\t}, actor)\n\t\t},\n\t)","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/merge_slot.go#L149-L185","documentation":"The release was attempted by a holder whose name does not match the current holder recorded in the slot metadata. MergeSlotRelease only allows the actual holder (or an empty holder string meaning force-release) to release the slot, preventing one agent from releasing another's lock.","triggerScenarios":"MergeSlotRelease called with holder != meta.Holder and holder != \"\" — e.g. two agents use the same holder name configured differently (hostname changed), a stale process releases after the holder was force-released and re-acquired by someone else, or a typo in the holder argument.","commonSituations":"Agent hostname/container name changed between acquire and release; slot re-acquired by another agent after a timeout/force release while the original holder still tries to clean up; manual force-release then legitimate release arriving late.","solutions":["Confirm the holder name used at acquire matches the one passed at release (same hostname/container identity)","Use bd merge-slot check to see the current holder before releasing","If you are an admin, release with an empty holder string to force-release, then re-acquire","If the holder crashed, force-release and let the new owner take the slot"],"exampleFix":"// before\nerr := store.MergeSlotRelease(ctx, \"old-hostname\", actor) // slot held by \"new-hostname\"\n// after\nstatus, _ := store.MergeSlotCheck(ctx)\nif status.Holder == \"old-hostname\" {\n    err = store.MergeSlotRelease(ctx, \"old-hostname\", actor)\n}","handlingStrategy":"validation","validationCode":"status, err := store.MergeSlotCheck(ctx)\nif err != nil { return err }\nif status.Holder != myHolder {\n    return fmt.Errorf(\"not the holder: slot held by %q\", status.Holder)\n}","typeGuard":"func isHolder(status *MergeSlotStatus, holder string) bool {\n    return status != nil && status.Holder == holder\n}","tryCatchPattern":"if err := store.MergeSlotRelease(ctx, holder, actor); err != nil && strings.Contains(err.Error(), \"slot held by\") {\n    // Someone else owns the slot; log and skip release (or force-release with \"\" if authorized).\n    log.Printf(\"skip release: %v\", err)\n    return nil\n}","preventionTips":["Pass the exact same holder identity used at acquire (stable hostname/container name)","Call MergeSlotCheck first to confirm current holder before releasing","Pass holder=\"\" only when an admin force-release is intended","Handle re-acquisition by others: a late release after force-release will fail with this error — treat it as non-fatal"],"tags":["go","merge-slot","lock-owner-mismatch","concurrency"],"backgroundTag":"slot-held-by-other-holder","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}