gastownhall/beads · error
db: MovePersistence %s: %w
Error message
db: MovePersistence %s: %w
What it means
Wraps failure from issueops.MoveIssuePersistenceInTx after the issue row was successfully read. This is the actual move operation failing (promote/demote between tables, event recording, or related mutations inside the transaction). The 'get issue' stage already succeeded at this point.
Source
Thrown at internal/storage/domain/db/issue.go:130
// the exact issueops implementation the classic (direct/embedded) route runs,
// so the two modes cannot drift. The issueops error is returned unwrapped on
// purpose: the CLI surfaces it verbatim ("wisp <id> not found"), and that
// text is part of the classic error contract.
func (r *issueSQLRepositoryImpl) PromoteFromEphemeral(ctx context.Context, id, actor string) error {
if id == "" {
return errors.New("db: PromoteFromEphemeral: id must not be empty")
}
return issueops.PromoteFromEphemeralInTx(ctx, r.runner, id, actor)
}
func (r *issueSQLRepositoryImpl) MovePersistence(ctx context.Context, id string, mode types.PersistenceMode, actor string) (bool, error) {
issue, err := issueops.GetIssueInTx(ctx, r.runner, id)
if err != nil {
return false, fmt.Errorf("db: MovePersistence %s: get issue: %w", id, err)
}
result, err := issueops.MoveIssuePersistenceInTx(ctx, r.runner, issue, mode, actor)
if err != nil {
return false, fmt.Errorf("db: MovePersistence %s: %w", id, err)
}
return result.Changed, nil
}
func (r *issueSQLRepositoryImpl) Update(ctx context.Context, id string, updates map[string]any, actor string, opts domain.IssueTableOpts) error {
if id == "" {
return errors.New("db: Update: id must not be empty")
}
if len(updates) == 0 {
return nil
}
updates = cloneUpdateFields(updates)
// Pop the close-policy override before anything reads the map as a set of
// columns, mirroring issueops.updateIssueInTx. The no-op filter below keeps
// unrecognized keys, so a surviving override would reach the field
// allowlist and be refused by name.
forceClosePolicy := issueops.PopForceClosePolicy(updates)
View on GitHub (pinned to 71377f2769)
Solutions
- Check the wrapped error for constraint violations or lock timeouts
- Retry with backoff if it was a lock conflict; use retry-safe logic since the transaction rolled back
- Verify both source and target tables exist and are migrated
- Reduce concurrency: ensure only one process mutates the issue at a time
Example fix
// before
changed, err := repo.MovePersistence(ctx, id, mode, actor)
if err != nil { panic(err) }
// after
changed, err := repo.MovePersistence(ctx, id, mode, actor)
if err != nil {
if isLockTimeout(err) { time.Sleep(time.Second); retry() }
return fmt.Errorf("move %s: %w", id, err)
} Defensive patterns
Strategy: retry
Validate before calling
if _, err := repo.Get(ctx, id, opts); err != nil {
return fmt.Errorf("cannot move missing issue %s", id)
} Type guard
func isRetryableMoveErr(err error) bool {
return isLockTimeoutErr(err) || errors.Is(err, context.DeadlineExceeded) || errors.Is(err, driver.ErrBadConn)
} Try / catch
var changed bool
err := retry(3, backoff, func() error {
var err error
changed, err = repo.MovePersistence(ctx, id, mode, actor)
if err != nil && !isRetryableMoveErr(err) { return stopRetry(err) }
return err
})
if err != nil { return fmt.Errorf("move %s failed after retries: %w", id, err) } Prevention
- Serialize persistence moves per issue (row lock or single worker)
- Ensure both wisp and persistent tables are migrated
- Retry with backoff; the transaction rolls back safely on failure
When it happens
Trigger: Calling MovePersistence when the move transaction fails: constraint violations moving rows between persistent/wisp tables, event-record insert failure, lock conflicts with concurrent writers on the same issue, or connection loss mid-transaction.
Common situations: Two agents moving the same issue concurrently; orphaned references blocking an ephemeral-to-persistent promote; disk full or replication issues during write.
Related errors
- db: MovePersistence %s: get issue: %w
- ErrTransaction
- open unit of work: %w
- failed to begin transaction: %w
- failed to commit is_blocked repairs: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/511f269ea9561753.
Report an issue: GitHub.