gastownhall/beads · error
step %s already claimed (status: %s)
Error message
step %s already claimed (status: %s)
What it means
The same step-advance transaction only allows claiming a step whose status is `open`. If the issue exists but is already `in_progress`, `closed`, or otherwise non-open, the port returns `step %s already claimed (status: %s)` to signal a concurrent/duplicate claim rather than silently overwriting state.
Source
Thrown at cmd/bd/mol_port.go:110
// transaction rather than seeing the last committed value.
func (w storeMolWriter) GetConfig(ctx context.Context, key string) (string, error) {
if w.tx != nil {
return w.tx.GetConfig(ctx, key)
}
return w.DoltStorage.GetConfig(ctx, key)
}
func (w storeMolWriter) ClaimStepIfOpen(ctx context.Context, id, actor string) error {
return w.DoltStorage.RunInTransaction(ctx, fmt.Sprintf("bd: advance to step %s", id), func(tx storage.Transaction) error {
current, err := tx.GetIssue(ctx, id)
if err != nil {
return err
}
if current == nil {
return fmt.Errorf("step %s not found", id)
}
if current.Status != types.StatusOpen {
return fmt.Errorf("step %s already claimed (status: %s)", id, current.Status)
}
return tx.UpdateIssue(ctx, id, map[string]interface{}{"status": types.StatusInProgress}, actor)
})
}
func newStandaloneStoreMolWriter(store storage.DoltStorage) storeMolWriter {
return storeMolWriter{DoltStorage: store}
}
type uowMolReader struct {
uw uow.UnitOfWork
}
func (r uowMolReader) GetIssue(ctx context.Context, id string) (*types.Issue, error) {
issue, isWisp, rerr := workapi.GetIssueOrWisp(ctx, workapi.NewUOWDetailSource(r.uw), id)
if errors.Is(rerr, storage.ErrNotFound) {
return nil, fmt.Errorf("issue %s not found", id)
}View on GitHub (pinned to 71377f2769)
Solutions
- Check the step's status first (`bd show <id>`) and skip the claim if it is already in_progress.
- Coordinate claim work so only one worker claims a step (the claim itself is transactional — treat this error as 'someone else got it' and move on).
- If the step is genuinely stuck, reset its status to open (`bd update <id> --status open`) before re-claiming.
Example fix
// before
_ = writer.AdvanceToStep(ctx, id, actor) // blind claim, fails if already claimed
// after
if step, _ := reader.GetIssue(ctx, id); step != nil && step.Status == types.StatusOpen {
err = writer.AdvanceToStep(ctx, id, actor)
} Defensive patterns
Strategy: validation
Validate before calling
// before claiming, check the step is still open
STATUS=$(bd show "$STEP_ID" --json | jq -r '.status')
[[ "$STATUS" == "open" ]] || { echo "step $STEP_ID is $STATUS, skipping"; exit 0; } Type guard
func claimable(iss *types.Issue) bool {
return iss != nil && iss.Status == types.StatusOpen
} Try / catch
if err := advance(ctx, stepID, actor); err != nil && strings.Contains(err.Error(), "already claimed") {
log.Printf("step %s taken by another worker; skipping", stepID)
return nil // expected in concurrent claiming
} Prevention
- Treat this error as a normal concurrency outcome, not a crash — skip and pick another step.
- Check status immediately before claiming to shrink the race window.
- Have one coordinator assign steps when many workers target one molecule.
When it happens
Trigger: Two agents or invocations advancing the same molecule step concurrently; re-running a claim script without checking status first; a step that was previously claimed and never reset. `current.Status != types.StatusOpen` triggers the error inside the `RunInTransaction` callback.
Common situations: CI retries after a partial failure; parallel workers racing on the same molecule; a human manually started the step (`bd update <id> --status in_progress`) and automation then tries to claim it.
Related errors
- failed to close issue: %s
- lock busy: held by another process
- lock already held by another process
- lock already held by another process
- errStartInterrupted
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/afb2790727969cfa.
Report an issue: GitHub.