{"record":{"id":"dec573e27e891270","repo":"gastownhall/beads","slug":"release-checked-close-savepoint-w","errorCode":null,"errorMessage":"release checked close savepoint: %w","messagePattern":"release checked close savepoint: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/close.go","lineNumber":259,"sourceCode":"\nfunc rollbackAndReleaseCloseCheckedSavepoint(ctx context.Context, tx DBTX, name string) error {\n\trollbackErr := rollbackToCloseCheckedSavepoint(ctx, tx, name)\n\treleaseErr := releaseCloseCheckedSavepoint(ctx, tx, name)\n\treturn errors.Join(rollbackErr, releaseErr)\n}\n\nfunc rollbackToCloseCheckedSavepoint(ctx context.Context, tx DBTX, name string) error {\n\t//nolint:gosec // G201: name is generated by createCloseCheckedSavepoint.\n\tif _, err := tx.ExecContext(ctx, \"ROLLBACK TO SAVEPOINT \"+name); err != nil {\n\t\treturn fmt.Errorf(\"rollback checked close savepoint: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc releaseCloseCheckedSavepoint(ctx context.Context, tx DBTX, name string) error {\n\t//nolint:gosec // G201: name is generated by createCloseCheckedSavepoint.\n\tif _, err := tx.ExecContext(ctx, \"RELEASE SAVEPOINT \"+name); err != nil {\n\t\treturn fmt.Errorf(\"release checked close savepoint: %w\", err)\n\t}\n\treturn nil\n}\n\n// dependencyTargetColumnForIDInTx returns the typed target column for id,\n// preferring a durable row when an id is present in both tables.\nfunc dependencyTargetColumnForIDInTx(ctx context.Context, tx DBTX, id string) (string, error) {\n\tvar found int\n\terr := tx.QueryRowContext(ctx, \"SELECT 1 FROM issues WHERE id = ?\", id).Scan(&found)\n\tif err == nil {\n\t\treturn \"depends_on_issue_id\", nil\n\t}\n\tif !errors.Is(err, sql.ErrNoRows) {\n\t\treturn \"\", fmt.Errorf(\"classify child-count target in issues: %w\", err)\n\t}\n\terr = tx.QueryRowContext(ctx, \"SELECT 1 FROM wisps WHERE id = ?\", id).Scan(&found)\n\tif err == nil {\n\t\treturn \"depends_on_wisp_id\", nil","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/close.go#L241-L277","documentation":"Beads throws this when 'RELEASE SAVEPOINT <name>' fails after a checked close. Releasing discards the savepoint once the trial has succeeded (or been rolled back); a failure here means the transaction or connection is in trouble, even though the close itself may have been applied. It is often reported together with a rollback error via errors.Join.","triggerScenarios":"CloseIssueCheckedInTx or rollbackAndReleaseCloseCheckedSavepoint calling RELEASE SAVEPOINT when the transaction was already rolled back (deadlock/timeout), the savepoint was already released, or the connection failed after the close UPDATE succeeded.","commonSituations":"Engine implicitly rolled back the transaction after a prior statement error; connection drop between the close UPDATE and RELEASE; driver that auto-releases savepoints on statement error.","solutions":["Treat as a transaction-level failure: retry the entire close operation on a fresh transaction","Look for an earlier joined error (errors.Join output) — the first error usually identifies the root cause","Check backend/driver support for RELEASE SAVEPOINT semantics; upgrade driver if incomplete","Investigate recurring deadlocks/lock timeouts if this happens under concurrent closes"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Check transaction liveness before close-policy operations\nif err := tx.QueryRowContext(ctx, \"SELECT 1\").Scan(&one); err != nil {\n\treturn fmt.Errorf(\"transaction not viable for checked close: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"err := CloseIssueCheckedInTx(ctx, tx, id, opts)\nif err != nil {\n\tif dberrors.IsConnectionError(err) || dberrors.IsDeadlock(err) {\n\t\t// savepoint may have been implicitly destroyed — restart on a fresh tx\n\t\treturn retryCloseOnNewTx(ctx, id)\n\t}\n\treturn err\n}","preventionTips":["Retry the whole close on fresh transactions after savepoint release failures","Read errors.Join output: the first joined error usually names the real cause","Avoid extremely long transactions that raise the odds of implicit rollback","Keep driver/server versions aligned so savepoint semantics are consistent"],"tags":["sql","savepoint","transaction"],"backgroundTag":"savepoint-release-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}