gastownhall/beads · error

db: Claim %s: record event: %w

Error message

db: Claim %s: record event: %w

What it means

This error wraps a failure to record the 'claimed' event (with old/new JSON snapshots) via r.events.Record after a successful claim. The event journal is part of the claim's transactional semantics; if recording fails, the whole Claim is rolled back with this wrapped error.

Source

Thrown at internal/storage/domain/db/issue.go:534

	// Grant the lease in the ephemeral leases table, mirroring
	// issueops.ClaimIssueInTx. Wisps are never leased. This dual must stay in
	// lockstep with the primary path (see the row_lock comment above).
	if !opts.UseWispsTable {
		if err := issueops.UpsertLeaseInTx(ctx, r.runner, id, actor, now, issueops.LeaseTTL(ctx)); err != nil {
			return domain.ClaimRowResult{}, fmt.Errorf("db: Claim %s: %w", id, err)
		}
	}

	oldData, _ := json.Marshal(oldIssue)
	newData, _ := json.Marshal(map[string]any{"assignee": actor, "status": "in_progress"})
	if err := r.events.Record(ctx, domain.Event{
		IssueID:  id,
		Type:     types.EventType("claimed"),
		Actor:    actor,
		OldValue: string(oldData),
		NewValue: string(newData),
	}, domain.RecordEventOpts{UseWispsTable: opts.UseWispsTable}); err != nil {
		return domain.ClaimRowResult{}, fmt.Errorf("db: Claim %s: record event: %w", id, err)
	}
	// A claim changes assignee and status; the lost-CAS path returns above
	// without writing and journals nothing.
	if err := issueops.RecordEventInTx(ctx, r.runner, issueops.EventUpdate, id, actor); err != nil {
		return domain.ClaimRowResult{}, err
	}

	return domain.ClaimRowResult{
		Updated:          true,
		CurrentAssignee:  actor,
		CurrentStatus:    types.StatusInProgress,
		StartedAtWasZero: startedWasZero,
		OldIssue:         oldIssue,
	}, nil
}

func (r *issueSQLRepositoryImpl) Get(ctx context.Context, id string, opts domain.IssueTableOpts) (*types.Issue, error) {
	if id == "" {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped cause (%w) for the underlying SQL/driver error.
  2. Verify the events table schema and integrity; re-run migrations if drifted.
  3. Check payload size — unusually large issue snapshots can exceed column limits.
  4. Retry the claim; the transaction rolled back so nothing was partially written.

Example fix

// before
res, err := store.Claim(ctx, id, actor, opts) // event write fails silently retried
// after
if err != nil {
    if errors.Is(err, context.Canceled) { return err } // do not blind-retry
    res, err = store.Claim(ctx, id, actor, opts) // transactional: safe to retry
}
Defensive patterns

Strategy: try-catch

Validate before calling

if err := ctx.Err(); err != nil { return err }
issue, err := store.Get(ctx, id, opts)
if err != nil { return err }
if len(issue.Description) > maxEventPayload { return fmt.Errorf("snapshot too large") }

Try / catch

res, err := store.Claim(ctx, id, actor, opts)
switch {
case err == nil:
    // claimed and journaled
case errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded):
    return err // do not retry blindly
default:
    res, err = store.Claim(ctx, id, actor, opts) // transactional rollback: safe retry
}

Prevention

When it happens

Trigger: Calling Claim where the events.Record call fails: events table write error, connection loss, oversized old/new payloads, or context cancellation mid-write.

Common situations: Events table corruption or schema drift; very large issue snapshots (oldData/newData JSON) hitting column limits; network failures to remote database during event write.

Related errors


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