{"record":{"id":"0236307953881f11","repo":"gastownhall/beads","slug":"discard-checked-close-savepoint-after-v-w","errorCode":null,"errorMessage":"discard checked close savepoint after %v: %w","messagePattern":"discard checked close savepoint after (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"internal/storage/issueops/close.go","lineNumber":95,"sourceCode":"\t\treturn nil, err\n\t}\n\tif !found {\n\t\treturn nil, fmt.Errorf(\"%w: issue %s\", storage.ErrNotFound, id)\n\t}\n\t// domain/db also supplies a *sql.DB Runner, whose independently pooled\n\t// statements cannot retain a savepoint. The shared UOW uses a pinned\n\t// *sql.Conn after START TRANSACTION, while embedded callers use *sql.Tx.\n\tif !closeCheckedSavepointEligible(tx) {\n\t\treturn closeIssueCheckedAfterSavepoint(ctx, tx, id, reason, actor, session, force, closed, targetColumn)\n\t}\n\tsavepoint, err := createCloseCheckedSavepoint(ctx, tx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresult, scopedErr := closeIssueCheckedAfterSavepoint(ctx, tx, id, reason, actor, session, force, closed, targetColumn)\n\tif scopedErr != nil {\n\t\tif cleanupErr := rollbackAndReleaseCloseCheckedSavepoint(ctx, tx, savepoint); cleanupErr != nil {\n\t\t\treturn nil, fmt.Errorf(\"discard checked close savepoint after %v: %w\", scopedErr, cleanupErr)\n\t\t}\n\t\treturn nil, scopedErr\n\t}\n\tif err := releaseCloseCheckedSavepoint(ctx, tx, savepoint); err != nil {\n\t\tif rollbackErr := rollbackToCloseCheckedSavepoint(ctx, tx, savepoint); rollbackErr != nil {\n\t\t\treturn nil, fmt.Errorf(\"release checked close savepoint: %v; rollback to savepoint: %w\", err, rollbackErr)\n\t\t}\n\t\treturn nil, err\n\t}\n\treturn result, nil\n}\n\nfunc closeCheckedSavepointEligible(tx DBTX) bool {\n\tswitch tx.(type) {\n\tcase *sql.Tx, *sql.Conn:\n\t\treturn true\n\tdefault:\n\t\treturn false","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/close.go#L77-L113","documentation":"After a checked close failed inside its savepoint, the cleanup (ROLLBACK TO SAVEPOINT + RELEASE SAVEPOINT) also failed. The library discards the original error's visibility and surfaces this compound error, because an un-released savepoint would corrupt the surrounding transaction's state. The message embeds the original scoped error (%v) and the cleanup error (%w).","triggerScenarios":"closeIssueCheckedAfterSavepoint returned an error AND rollbackAndReleaseCloseCheckedSavepoint also failed — typically because the transaction/connection is dead (server gone, connection closed) so even ROLLBACK TO SAVEPOINT cannot execute.","commonSituations":"Dolt server connection dropped during the close; transaction already marked failed by a prior SQL error the driver treats as aborting; context canceled mid-close killing both the statement and the cleanup.","solutions":["Treat the transaction as unusable: roll back the whole transaction and start over","Inspect both the embedded scoped error (%v prefix) and the cleanup cause (%w) for the root failure","Retry the close in a fresh transaction after connectivity is restored","If contexts cancel frequently, ensure the context passed to the close outlives the operation"],"exampleFix":"err := store.CloseIssue(ctx, id, reason, actor)\nif err != nil && strings.Contains(err.Error(), \"discard checked close savepoint\") {\n\t// transaction is poisoned — abandon it entirely\n\ttx.Rollback()\n\ttx, err = db.BeginTx(ctx, nil)\n\t// retry close in the fresh transaction\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"err := store.CloseIssue(ctx, id, reason, actor)\nif err != nil && strings.Contains(err.Error(), \"discard checked close savepoint\") {\n\t// transaction is poisoned: roll back everything, do not commit\n\t_ = tx.Rollback()\n\treturn retryInFreshTransaction(ctx, id)\n}","preventionTips":["Never commit a transaction after savepoint cleanup failed","Use savepoint-eligible handles (*sql.Tx or *sql.Conn), not pooled *sql.DB runners","Ensure contexts outlive the close operation; monitor connection health"],"tags":["database","savepoint","transaction"],"backgroundTag":"savepoint-cleanup-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}