Skip to content

bun:sqlite: close() finalizes outstanding statements instead of leaving them live - #33307

Closed
robobun wants to merge 6 commits into
mainfrom
farm/a9a2222f/sqlite-close-finalizes-statements
Closed

robobun wants to merge 6 commits into
mainfrom
farm/a9a2222f/sqlite-close-finalizes-statements

Conversation

@robobun

@robobun robobun commented Jul 3, 2026 •

Copy link
Copy Markdown
Collaborator

Repro

import { Database } from "bun:sqlite";

const db = new Database("data.db");
db.exec("create table t (v text); insert into t values ('hello')");
const q = db.prepare("select v from t");

db.close();

q.get();   // => { v: "hello" }  (better-sqlite3 / node:sqlite throw)
q.run();   // => throws "Database has closed"
// the database fd is still open; close() cannot be relied on to release it

One statement object half-works after close(): the read paths return rows, the write path throws. close(true) with an outstanding statement reports database is locked.

Cause

close() calls sqlite3_close_v2(), which only defers the close while prepared statements are outstanding, then nulls VersionSqlite3::db. Only the write and prepare entry points check for the null handle, so get()/all()/values()/iterate() keep stepping the still-live sqlite3_stmt*, and SQLite keeps the whole connection (and the database file) open until the last statement is finalized. For close(true), sqlite3_close() returns SQLITE_BUSY, whose generic error string blames a locking conflict rather than the unfinalized statements.

Fix

VersionSqlite3 now tracks the statements prepared against it, and close(false) finalizes them, the same way clearQueryCache() already finalizes the cached ones. CHECK_PREPARED reports Database has closed once the handle is gone, so every statement entry point fails instead of just run().

close(true) still refuses to close while statements are outstanding, which is its documented contract, but now says why. sqlite3_close() returns SQLITE_BUSY only for unfinalized statements and backups, and bun:sqlite exposes no backup API.

Two things deliberately keep working after close(), exactly as they do after stmt.finalize() today, because neither reads the closed database:

  • columnNames, which is cached from the statement's last execution
  • toString(), which backs the statement's Symbol.toStringTag (src/js/bun/sqlite.ts:172), so making it throw would break console.log(stmt)

The database object being garbage collected while statements are alive is unchanged: that path is refcounted through VersionSqlite3::release() and the statements keep working, as can continue to use existing statements after database has been GC'd covers.

Rebase note

The db.run() close-during-bind use-after-free this originally also fixed (a binding getter calling db.close() frees the sqlite3* mid-call) has since landed on main via #33072, with the same guard. The conflict resolution keeps main's condition and this PR's comment explaining why the sqlite3* is freed at that point. The rejects db.run(%p) when a binding getter closes the database tests here still exercise that guard but also assert that the bystander statement throws Database has closed afterwards, which is this PR's CHECK_PREPARED change.

Verification

bun bd test test/js/bun/sqlite/ — 106 pass, 0 fail. The new and updated assertions fail on main:

git checkout origin/main -- src/ && bun bd test test/js/bun/sqlite/sqlite.test.js -t "statement still alive"
(fail) makes the statement throw instead of reading the closed database
(fail) leaves the statement inspectable, the way finalize() does
(fail) releases the database file
(fail) stops an in-progress iteration
(fail) rejects a statement whose database is closed while binding
(fail) rejects db.run("INSERT INTO t VALUES ($a)") when a binding getter closes the database
(fail) rejects db.run("INSERT INTO t VALUES ($a); INSERT INTO t VALUES (2)") when a binding getter closes the database
(pass) stays safe when the statement is garbage collected afterwards
 1 pass
 7 fail

plus close(true) should throw an error if the database is in use and should dispose AND throw an error if the database is in use, which assert the new message. The debug build runs under ASAN.

test/js/sql/sqlite-sql.test.ts has two tests that time out in debug+ASAN builds (properly finalizes prepared statements, handles exotic but valid SQL patterns). Both time out identically on main.

@github-actions github-actions Bot added the claude label Jul 3, 2026
@robobun

robobun commented Jul 3, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 9:38 AM PT - Jul 7th, 2026

❌ @robobun, your commit 6f31d15 has some failures in Build #69849 (All Failures)


🧪   To try this PR locally:

bunx bun-pr 33307

That installs a local version of the PR into your bun-33307 executable, so you can run:

bun-33307 --bun

@mintlify

mintlify Bot commented Jul 3, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
bun 🟢 Ready View Preview Jul 3, 2026, 11:17 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Found 2 issues this PR may fix:

  1. bun:sqlite not closable after running migrations #11418 - close(true) fails with "database is locked" when prepared statements from migrations are still open; this PR auto-finalizes them on close() and gives a clear error on close(true)
  2. SQLITE Locked in WAL mode #29494 - close(true) after database.transaction() fails with "database is locked" due to unfinalized statements; same root cause fixed by this PR's outstanding statement tracking and finalization

If this is helpful, copy the block below into the PR description to auto-close these issues on merge.

Fixes #11418
Fixes #29494

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 3, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

SQLite close handling now finalizes tracked prepared statements on close(false), reports a specific error on close(true) when statements remain open, and treats statements as closed after their database is closed. Documentation and tests were updated to match the new behavior.

Changes

SQLite close() statement lifecycle

Layer / File(s) Summary
Statement tracking and CHECK_PREPARED updates
src/jsc/bindings/sqlite/JSSQLStatement.cpp
VersionSqlite3 now tracks live JSSQLStatement* wrappers, new statements register on creation, destruction removes them from the tracked set, and CHECK_PREPARED throws "Database has closed" when the underlying db handle is missing.
Close function finalization behavior
src/jsc/bindings/sqlite/JSSQLStatement.cpp
finalizeOutstandingStatements finalizes tracked statements and clears their stmt pointers; jsSQLStatementCloseStatementFunction uses it for close(false), clears tracked statements after close, and emits a specific SQLITE_BUSY error for close(true).
SQLite close() docs
docs/runtime/sqlite.mdx, packages/bun-types/sqlite.d.ts
The .close(false) and .close(true) sections now describe statement finalization, file release, open-statement errors, post-close statement use, and garbage-collection behavior.
SQLite close() runtime tests
test/js/bun/sqlite/sqlite.test.js
Tests update close error expectations and add coverage for statement access after close, inspectability, WAL cleanup, interrupted iteration, binding-time close, db.run() close cases, and GC safety.

Possibly related PRs

  • oven-sh/bun#31495: Also changes SQLite JS binding lifecycle behavior in src/jsc/bindings/sqlite/JSSQLStatement.cpp, including jsSQLStatementCloseStatementFunction and versionDB close handling.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: Database.close() now finalizes outstanding statements instead of leaving them live.
Description check ✅ Passed The description covers the problem, cause, fix, edge cases, and verification, even though it uses different headings than the template.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/runtime/sqlite.mdx (1)

113-136: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Docs accurately match the new close() semantics. Minor wording nit on line 131.

✏️ Suggested wording tweak
-Using a statement after the database it came from was closed throws `Database has closed`.
+Using a statement after its database has been closed throws `Database has closed`.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/runtime/sqlite.mdx` around lines 113 - 136, The `Database.close()` docs
already reflect the new semantics, but the wording around statement finalization
is a bit awkward. Update the explanatory text in the `close(throwOnError:
boolean = false)` section to use clearer phrasing for the behavior of prepared
statements when `Database.close(false)` is called, while keeping the `Database`
and `close(true)` examples unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/jsc/bindings/sqlite/JSSQLStatement.cpp`:
- Around line 191-203: `jsSQLStatementToStringFunction` and
`jsSqlStatementGetColumnNames` bypass the existing statement-lifecycle guard and
can dereference a finalized or closed statement; add the same `CHECK_PREPARED`
protection used by the rest of `JSSQLStatement.cpp` before calling
`sqlite3_expanded_sql` or `sqlite3_column_count`. Use the existing `castedThis`,
`stmt`, and `version_db` checks to reject post-close access consistently with
other methods, and make both functions return the same thrown error path as the
prepared-statement API.

In `@test/js/bun/sqlite/sqlite.test.js`:
- Around line 1391-1409: The “every statement entry point” test is missing
several `PreparedStatement` APIs, so extend the `close() with a statement still
alive` case to cover `prepared.columns`, `prepared.toString()`,
`prepared.raw()`, `prepared.columnsCount`, and `prepared.safeIntegers` in
addition to the existing methods. Use the `Database` and `prepared` setup
already in this test, and add assertions that each of these entry points now
throws after `db.close()`, so the coverage matches the test name and catches the
closed-database guard gap.

---

Outside diff comments:
In `@docs/runtime/sqlite.mdx`:
- Around line 113-136: The `Database.close()` docs already reflect the new
semantics, but the wording around statement finalization is a bit awkward.
Update the explanatory text in the `close(throwOnError: boolean = false)`
section to use clearer phrasing for the behavior of prepared statements when
`Database.close(false)` is called, while keeping the `Database` and
`close(true)` examples unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0c0ac6c6-037b-44ee-8212-7d3ee24f5627

📥 Commits

Reviewing files that changed from the base of the PR and between 1498d7b and 5832d52.

📒 Files selected for processing (3)
  • docs/runtime/sqlite.mdx
  • src/jsc/bindings/sqlite/JSSQLStatement.cpp
  • test/js/bun/sqlite/sqlite.test.js

Comment thread src/jsc/bindings/sqlite/JSSQLStatement.cpp
Comment thread test/js/bun/sqlite/sqlite.test.js
@robobun

robobun commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator Author

Checked both; this PR closes neither, so I'm leaving the description as is.

#29494 (close(true) after database.transaction()) already passes on main. It was fixed by #27202, which finalizes the transaction controller's statements in Database#close. Against main:

#29494 close(true): OK

#11418 (close(true) after drizzle migrations) still throws on this branch. Only close(false) finalizes outstanding statements; close(true) is documented to fail while queries are outstanding, and [Symbol.dispose] relies on that to surface a leaked statement. All this PR changes there is the message:

before: #11418 close(true) threw: database is locked
after:  #11418 close(true) threw: Cannot close the database because prepared statements are still open. Finalize them first, or call close(false).

Making close(true) finalize idle statements, and throw only when one is actually mid-step, would fix #11418 and is arguably what "throw an error if there are any pending queries" means. It also changes what using db = new Database(...) does with a leaked statement, so it is a separate call from this bug fix. Happy to do it in a follow-up if that's the direction you want.

Comment thread src/jsc/bindings/sqlite/JSSQLStatement.cpp
Comment thread docs/runtime/sqlite.mdx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/runtime/sqlite.mdx`:
- Around line 133-136: Clarify the GC behavior in the SQLite docs note so it
does not imply the connection is fully released when the database is garbage
collected. Update the wording around the database/connection lifecycle in the
`<Note>` text to state that GC only releases the database object itself, while
outstanding statements may continue to keep the underlying connection/file alive
until they are finalized or collected; keep the distinction from `close()`
explicit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 22ecd71d-bcd2-47de-9fe2-2e64d4b203cc

📥 Commits

Reviewing files that changed from the base of the PR and between f27cd32 and 7c98ff9.

📒 Files selected for processing (3)
  • docs/runtime/sqlite.mdx
  • src/jsc/bindings/sqlite/JSSQLStatement.cpp
  • test/js/bun/sqlite/sqlite.test.js

Comment thread docs/runtime/sqlite.mdx Outdated
Comment thread src/jsc/bindings/sqlite/JSSQLStatement.cpp
Comment thread docs/runtime/sqlite.mdx
@robobun
robobun requested a review from alii as a code owner July 3, 2026 13:00

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/bun-types/sqlite.d.ts`:
- Around line 274-283: The `Database.close` doc comment has free-text paragraphs
after the `@example`, which is inconsistent with `run`, `query`, and `prepare`
and can render poorly in TypeDoc/IDE tooltips. Reorder the JSDoc so the
garbage-collection and `sqlite3_close_v2` prose appears before the `@example`
block, and keep `@example` as the final tag in the `close` documentation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4b9871f4-d655-470c-b981-f7a714afd48f

📥 Commits

Reviewing files that changed from the base of the PR and between 5ef3468 and 1a3d147.

📒 Files selected for processing (1)
  • packages/bun-types/sqlite.d.ts

Comment thread packages/bun-types/sqlite.d.ts Outdated
@robobun

robobun commented Jul 3, 2026 •

Copy link
Copy Markdown
Collaborator Author

CI status: green on the diff, infra failures on darwin-aarch64-26-5-1-1

Rebased onto main (6f31d15) to clear a conflict with #33072, which independently landed the same db.run() close-during-bind guard this PR originally added. The resolution keeps main's condition (versionDB->db != db) and this PR's comment explaining why the sqlite3* is freed at that point. The PR is back to 6 substantive commits.

Locally after the rebase:

  • bun bd test test/js/bun/sqlite/ — 106 pass, 0 fail
  • bun test test/integration/bun-types/bun-types.test.ts — 12 pass, 0 fail
  • fail-before with origin/main's JSSQLStatement.cpp: 7 of 8 statement still alive tests fail

CI history

Three builds, and the only hard failures are macOS infra unrelated to this diff:

build result the one failure
68216 (f4f4ba40) 282 pass, 1 fail proxy-stress-concurrent.test.ts fixture can't resolve a harness import on agent macOS-13-x64-1 (stale checkout)
68221 (017768d7) 285 pass, 1 fail buildkite-agent artifact download timed out after 120s on agent darwin-aarch64-26-5-1-1, no tests ran
69849 (6f31d158, rebased) 285 pass, 1 fail buildkite-agent artifact download timed out after 120s on agent darwin-aarch64-26-5-1-1, no tests ran

The last two are the same agent timing out at the same line. test/js/bun/sqlite/sqlite.test.js itself ran 88 tests and passed on every lane that picked it up.

I am not pushing another retrigger. This needs a maintainer to merge through the flake (or to look at why darwin-aarch64-26-5-1-1 keeps dropping artifact downloads).

robobun added 6 commits July 7, 2026 11:02
…ng them live

sqlite3_close_v2() only marks the connection closed while prepared
statements are still alive, and close() nulled VersionSqlite3::db without
touching them. Only the write and prepare entry points checked for the
null handle, so get()/all()/values()/iterate() kept stepping the live
sqlite3_stmt* and returned rows from a closed database, and the database
file stayed open until the statements were garbage collected.

Track the statements prepared against each connection and finalize them
in close(false), the way the query cache already does, and report
"Database has closed" from every statement entry point once the
connection is gone. close(true) still refuses to close while statements
are outstanding, but says so instead of reporting "database is locked".
A parameter getter can close the database while db.run() is binding. The
statement it prepared internally is then the last one holding the connection
open, so finalizing it at the end of the loop iteration frees the sqlite3*
that the function still holds in a local: the next sqlite3_prepare_v3(), or
the trailing sqlite3_total_changes(), reads freed memory.

The Statement entry points re-validate after binding, but db.run() passes a
null JSSQLStatement to rebindStatement() so those guards never fire. Re-read
the handle instead, the way Statement#run already does.
@robobun
robobun force-pushed the farm/a9a2222f/sqlite-close-finalizes-statements branch from 017768d to 6f31d15 Compare July 7, 2026 11:12

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All prior review feedback has been addressed and I didn't find anything new, but this changes close()'s user-visible contract and adds raw-pointer statement tracking across a GC destructor path — worth a maintainer sign-off on the API semantics and lifetime model.

Extended reasoning...

Overview

This PR changes bun:sqlite's Database.close() to eagerly finalize every outstanding JSSQLStatement prepared against the connection, rather than relying on sqlite3_close_v2()'s deferred-close semantics. It touches four files: src/jsc/bindings/sqlite/JSSQLStatement.cpp (adds a Vector<JSSQLStatement*> on VersionSqlite3, populates it in JSSQLStatement::create, drains it in ~JSSQLStatement, adds finalizeOutstandingStatements(), extends CHECK_PREPARED to report Database has closed, and rewrites the SQLITE_BUSY message from close(true)), plus matching docs (docs/runtime/sqlite.mdx), type declarations (packages/bun-types/sqlite.d.ts), and ~140 lines of new tests in test/js/bun/sqlite/sqlite.test.js.

Security risks

None identified. The change tightens lifecycle handling rather than loosening it, and the input surface (JS-visible close() and statement methods) is unchanged. The one memory-safety hazard I raised earlier — a UAF in db.run() when a bindings getter closes the database mid-bind — was confirmed under ASAN by the author and the guard for it has since landed on main via #33072; this PR keeps that guard and adds a comment plus test coverage. The Vector<JSSQLStatement*> is non-owning, entries are added/removed on the JS thread only, and finalizeOutstandingStatements() swap-clears before iterating so a re-entrant ~JSSQLStatement running removeFirst on the (now-empty) member vector is safe.

Level of scrutiny

This warrants maintainer review. It is a behavioral change to a public API: code that previously kept using a prepared statement after db.close() (which half-worked — read paths returned rows) will now throw Database has closed. That's the correct behavior and matches better-sqlite3/node:sqlite, but it's still a user-visible contract change. It also adds raw-pointer bookkeeping across a GC-driven destructor (~JSSQLStatement → version_db->statements.removeFirst(this)), which is exactly the class of change CLAUDE.md's "Native code: memory safety" section flags as most-scrutinized. The implementation looks correct to me after several rounds, but a maintainer should own the sign-off on both the API decision and the lifetime model.

Other factors

The PR has been through substantial review iteration: two CodeRabbit threads (both resolved with reasoned pushback on toString()/columnNames staying unguarded), and four of my own inline comments — the db.run() UAF (fixed and now on main), the stale GC Note in docs (reworded twice), the O(N²) removeFirst in the destructor (benchmarked and left as-is with data showing the Vector beats a HashSet at every reachable N), and the stale .d.ts JSDoc (synced). All threads are resolved. Test coverage is thorough (every statement entry point after close, WAL sidecar release, mid-iteration close, close-during-bind for both Statement and db.run(), post-close GC safety, inspectability), and the author verified the suite fails on main and passes on the branch under ASAN. The only remaining call is whether the API-semantics change and the pointer-tracking design are what the maintainers want — that's not mine to make.

headygains pushed a commit to headygains/bun that referenced this pull request Aug 2, 2026
…ite3_next_stmt (oven-sh#36573)

## Repro

```js
import { Database } from "bun:sqlite";

const db = new Database(":memory:");
db.run("create table t (a integer)");

// 21 distinct SQL strings, each run through db.query()
for (let i = 0; i < 21; i++) db.query(`select a + ${i} as v from t`).all();

db.close(true); // error: database is locked
```

The threshold is exactly `Database.MAX_QUERY_CACHE_SIZE` (default 20).
With a file-backed database and non-strict `close()`, nothing throws but
the file handle stays open until GC, which on Windows makes the database
file undeletable right after closing it.

## Cause

`query()` only tracks the first `MAX_QUERY_CACHE_SIZE` distinct
statements; later ones are prepared but stored nowhere, so `close()`
could not finalize them and `sqlite3_close()` returned `SQLITE_BUSY`.

## Fix

Per review, the mechanism mirrors better-sqlite3's `CloseHandles()` and
node:sqlite's `FinalizeStatements()`:

- `VersionSqlite3` keeps a list of live `JSSQLStatement` wrappers,
linked at creation (next to the existing `reference_count` increment)
and unlinked in the destructor.
- `close()` walks that list, `sqlite3_finalize`s each statement and
nulls the wrapper's handle, then runs a `sqlite3_next_stmt()` sweep as a
backstop for statements not owned by a wrapper (e.g. the transient
statement in `db.run()` when a bound-parameter getter closes the
database mid-bind), then calls `sqlite3_close()`, which can no longer
return `SQLITE_BUSY`.
- Because a nulled handle is indistinguishable from an explicitly
finalized statement, the existing "Statement has finalized"
re-validation paths cover all post-close use; no new per-statement
state. The bind paths re-check the connection after running user code,
and `raw()`'s push-exception path no longer resets a statement that user
code may have finalized.
- If `sqlite3_close()` somehow still fails, the handle is retired via
`sqlite3_close_v2()` so the connection never ends up half-alive.

The termination path and GC `release()` still use `sqlite3_close_v2()`
without touching the list, so their outstanding statements stay valid
until each is finalized, as before.

Behavior change, reflected in docs and types: `close(true)` no longer
throws `database is locked` over outstanding prepared statements; it
finalizes them, matching better-sqlite3 and node:sqlite. Using a
statement after close throws `Statement has finalized`, matching node.

## Verification

Without the fix:

```
$ USE_SYSTEM_BUN=1 bun test test/js/bun/sqlite/sqlite.test.js -t 36572
(fail) close(true) finalizes query() statements created after the cache filled up (oven-sh#36572)
  error: database is locked
(fail) close(true) finalizes query() statements past the cache limit that are still referenced (oven-sh#36572)
```

With the fix: `bun bd test test/js/bun/sqlite/` 115 pass,
`test/js/node/sqlite/` 115 pass, `test/regression/issue/14709.test.ts` 5
pass, bun-types 14 pass.

Fixes oven-sh#36572

Closes oven-sh#35604
Closes oven-sh#33307 
Closes oven-sh#11418

<!-- robobun:evidence:begin -->

---

**no test proof** · iteration 4 · Platform-specific test(s) that do not
run on this machine. Deferring to CI, which covers all platforms:
test/js/bun/sqlite/sqlite.test.js

<!-- robobun:evidence:end -->

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

This branch was successfully deployed

1 active deployment
staging - docs — 6f31d158 Deployed Jul 7, 2026 by mintlify[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant