{"record":{"id":"511f269ea9561753","repo":"gastownhall/beads","slug":"db-movepersistence-s-w","errorCode":null,"errorMessage":"db: MovePersistence %s: %w","messagePattern":"db: MovePersistence (.+?): %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/issue.go","lineNumber":130,"sourceCode":"// the exact issueops implementation the classic (direct/embedded) route runs,\n// so the two modes cannot drift. The issueops error is returned unwrapped on\n// purpose: the CLI surfaces it verbatim (\"wisp <id> not found\"), and that\n// text is part of the classic error contract.\nfunc (r *issueSQLRepositoryImpl) PromoteFromEphemeral(ctx context.Context, id, actor string) error {\n\tif id == \"\" {\n\t\treturn errors.New(\"db: PromoteFromEphemeral: id must not be empty\")\n\t}\n\treturn issueops.PromoteFromEphemeralInTx(ctx, r.runner, id, actor)\n}\n\nfunc (r *issueSQLRepositoryImpl) MovePersistence(ctx context.Context, id string, mode types.PersistenceMode, actor string) (bool, error) {\n\tissue, err := issueops.GetIssueInTx(ctx, r.runner, id)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"db: MovePersistence %s: get issue: %w\", id, err)\n\t}\n\tresult, err := issueops.MoveIssuePersistenceInTx(ctx, r.runner, issue, mode, actor)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"db: MovePersistence %s: %w\", id, err)\n\t}\n\treturn result.Changed, nil\n}\n\nfunc (r *issueSQLRepositoryImpl) Update(ctx context.Context, id string, updates map[string]any, actor string, opts domain.IssueTableOpts) error {\n\tif id == \"\" {\n\t\treturn errors.New(\"db: Update: id must not be empty\")\n\t}\n\tif len(updates) == 0 {\n\t\treturn nil\n\t}\n\tupdates = cloneUpdateFields(updates)\n\t// Pop the close-policy override before anything reads the map as a set of\n\t// columns, mirroring issueops.updateIssueInTx. The no-op filter below keeps\n\t// unrecognized keys, so a surviving override would reach the field\n\t// allowlist and be refused by name.\n\tforceClosePolicy := issueops.PopForceClosePolicy(updates)\n","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/issue.go#L112-L148","documentation":"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.","triggerScenarios":"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.","commonSituations":"Two agents moving the same issue concurrently; orphaned references blocking an ephemeral-to-persistent promote; disk full or replication issues during write.","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"],"exampleFix":"// before\nchanged, err := repo.MovePersistence(ctx, id, mode, actor)\nif err != nil { panic(err) }\n// after\nchanged, err := repo.MovePersistence(ctx, id, mode, actor)\nif err != nil {\n    if isLockTimeout(err) { time.Sleep(time.Second); retry() }\n    return fmt.Errorf(\"move %s: %w\", id, err)\n}","handlingStrategy":"retry","validationCode":"if _, err := repo.Get(ctx, id, opts); err != nil {\n    return fmt.Errorf(\"cannot move missing issue %s\", id)\n}","typeGuard":"func isRetryableMoveErr(err error) bool {\n    return isLockTimeoutErr(err) || errors.Is(err, context.DeadlineExceeded) || errors.Is(err, driver.ErrBadConn)\n}","tryCatchPattern":"var changed bool\nerr := retry(3, backoff, func() error {\n    var err error\n    changed, err = repo.MovePersistence(ctx, id, mode, actor)\n    if err != nil && !isRetryableMoveErr(err) { return stopRetry(err) }\n    return err\n})\nif err != nil { return fmt.Errorf(\"move %s failed after retries: %w\", id, err) }","preventionTips":["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"],"tags":["database","issue","persistence","transaction"],"backgroundTag":"db-transaction-write-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}