{"record":{"id":"4ba5a992899d8d98","repo":"gastownhall/beads","slug":"create-checked-close-savepoint-w","errorCode":null,"errorMessage":"create checked close savepoint: %w","messagePattern":"create checked close savepoint: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/close.go","lineNumber":237,"sourceCode":"\t\t  AND child.status != 'closed'\n\t\t  AND NOT EXISTS (\n\t\t\tSELECT 1 FROM dependencies AS durable WHERE durable.id = dependency.id\n\t\t  )\n\t`, targetColumn)\n\tif err := tx.QueryRowContext(ctx, wispQuery, id).Scan(&wispCount); err != nil {\n\t\tif optionalBlockedTable(\"wisp_dependencies\") && isTableNotExistError(err) {\n\t\t\treturn durableCount, nil\n\t\t}\n\t\treturn 0, fmt.Errorf(\"count open wisp children for %s: %w\", id, err)\n\t}\n\treturn durableCount + wispCount, nil\n}\n\nfunc createCloseCheckedSavepoint(ctx context.Context, tx DBTX) (string, error) {\n\tname := closeCheckedSavepointPrefix + strconv.FormatUint(closeCheckedSavepointCounter.Add(1), 10)\n\t//nolint:gosec // G201: name is a fixed identifier-safe prefix plus an atomic decimal counter.\n\tif _, err := tx.ExecContext(ctx, \"SAVEPOINT \"+name); err != nil {\n\t\treturn \"\", fmt.Errorf(\"create checked close savepoint: %w\", err)\n\t}\n\treturn name, nil\n}\n\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","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/close.go#L219-L255","documentation":"Beads throws this when issuing 'SAVEPOINT <name>' fails during a checked close. CloseIssueCheckedInTx creates a savepoint so it can trial-run the close policy and roll back if children are still open. The savepoint name is internally generated (fixed prefix + atomic counter), so this error almost always reflects a database/engine limitation rather than bad input.","triggerScenarios":"Calling CloseIssue with check semantics when the underlying engine rejects SAVEPOINT: transaction not active (autocommit driver wrapper), engine without savepoint support, connection lost between BEGIN and SAVEPOINT, or too many nested savepoints.","commonSituations":"Using a driver/backend that doesn't support MySQL-style SAVEPOINT (some SQLite wrappers, older Dolt versions); a pooled connection whose transaction was already terminated; long transactions hitting engine savepoint-depth limits.","solutions":["Verify the storage backend and driver support SAVEPOINT inside a transaction (Dolt/MySQL do; ensure driver version is current)","Confirm the tx passed in is a live transaction — savepoints require an active transaction context","Upgrade the beads storage driver / Dolt if the error indicates unsupported statement syntax","Check connection health; reconnect and retry the close if the connection dropped"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"// Probe savepoint support before relying on checked close\nif _, err := tx.ExecContext(ctx, \"SAVEPOINT probe_beads_support\"); err != nil {\n\treturn fmt.Errorf(\"backend lacks SAVEPOINT support: %w\", err)\n}\n_, _ = tx.ExecContext(ctx, \"ROLLBACK TO SAVEPOINT probe_beads_support\")\n_, _ = tx.ExecContext(ctx, \"RELEASE SAVEPOINT probe_beads_support\")","typeGuard":null,"tryCatchPattern":"name, err := createCloseCheckedSavepoint(ctx, tx)\nif err != nil {\n\tif dberrors.IsConnectionError(err) {\n\t\treturn retryClose(ctx, id)\n\t}\n\t// Fallback: use plain (unchecked) close if the backend cannot savepoint\n\tlog.Printf(\"savepoint unavailable (%v); falling back to unchecked close\", err)\n\treturn closeUnchecked(ctx, tx, id)\n}","preventionTips":["Use a backend/driver with MySQL-style SAVEPOINT inside transactions (current Dolt or MySQL)","Never share/pass transactions across subsystems that manage their own savepoints","Keep transactions short to reduce the window for connection drops","Upgrade drivers that historically lacked savepoint passthrough"],"tags":["sql","savepoint","transaction"],"backgroundTag":"savepoint-unsupported","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}