gastownhall/beads · error

authorize assignee transfer %s: read claim pools: %w

Error message

authorize assignee transfer %s: read claim pools: %w

What it means

authorizeAssigneeTransfer checks whether an update changes the assignee in a way the claim-pool policy forbids. If the initial check (with nil pools) fails, it reads the 'claim.pools' config; failing to read that config produces this wrapped error. It is an infrastructure failure reading configuration, not a policy denial — a denial surfaces as the original AuthorizeAssigneeTransferWithPools error instead.

Source

Thrown at internal/storage/uow/issue_operations.go:285

	return true
}

// authorizeAssigneeTransfer applies the shared assignee-transfer fence to an
// update running in this unit of work. It is the sibling of the
// issueops.AuthorizeAssigneeTransfer call in ExecuteUpdate — the same predicate
// and the same refusal — reached through the config use case because a unit of
// work has no transaction handle to read claim.pools from. The read only
// happens once the transfer is otherwise fenced, and its failure propagates
// rather than being read as "no pools configured": treating an unreadable
// config as an empty alias set would refuse pool work the fence was never meant
// to touch.
func authorizeAssigneeTransfer(ctx context.Context, uw UnitOfWork, before *types.Issue, request publicops.UpdateRequest) error {
	if err := storageissueops.AuthorizeAssigneeTransferWithPools(before, request, nil); err == nil {
		return nil
	}
	raw, err := uw.ConfigUseCase().GetConfig(ctx, "claim.pools")
	if err != nil {
		return fmt.Errorf("authorize assignee transfer %s: read claim pools: %w", request.IssueID, err)
	}
	return storageissueops.AuthorizeAssigneeTransferWithPools(before, request, storageissueops.ParseClaimPools(raw))
}

func updateSpec(request publicops.UpdateRequest) (domain.UpdateSpec, error) {
	fields := make(map[string]any)
	patch := request.Patch
	if err := validateMetadataPatch(patch.Metadata); err != nil {
		return domain.UpdateSpec{}, validationError(err)
	}
	setField(fields, "title", patch.Title)
	setField(fields, "description", patch.Description)
	setField(fields, "design", patch.Design)
	setField(fields, "acceptance_criteria", patch.AcceptanceCriteria)
	setField(fields, "spec_id", patch.SpecID)
	setField(fields, "await_id", patch.AwaitID)
	setField(fields, "status", patch.Status)
	setField(fields, "priority", patch.Priority)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped error to see why the claim.pools config read failed.
  2. Initialize/repair the config store so 'claim.pools' is readable (e.g. bd doctor / migrations).
  3. Retry the update if the failure was transient (connection blip).
Defensive patterns

Strategy: fallback

Validate before calling

// ensure claim.pools config exists before assignee-changing updates
if _, err := cfg.GetConfig(ctx, "claim.pools"); err != nil { /* initialize config */ }

Try / catch

if err != nil && strings.Contains(err.Error(), "read claim pools") {
    // config infra failure: retry or repair config store
    return repairAndRetry()
}

Prevention

When it happens

Trigger: runUpdate hits an assignee change requiring pool authorization, and uw.ConfigUseCase().GetConfig(ctx, "claim.pools") errors (missing config store, query failure, connectivity).

Common situations: Config table absent after a fresh/failed initialization; DB unreachable during update; a UoW assembled without a ConfigUseCase (nil dereference path before the read).

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/f95508ba8c1a58d9. Report an issue: GitHub.