sqlite: document what survives a process kill

User asked what happens if the window is closed mid-task. Added a
'What happens if the process dies mid-task' section that tabulates
the four exit paths (TUI close, SIGTERM, SIGKILL, power loss) and
empirically confirms that anything past the last fsync'd commit is
on disk. The only ways to lose data are :memory:, an explicit
synchronous=OFF, or a network filesystem. Also corrects the earlier
'leak fd' framing — a dead process leaks no fds, the OS reclaims
them.
This commit is contained in:
opencode-agent
2026-10-08 17:22:13 +08:00
parent f873990e72
commit d1aabef771
+36
View File
@@ -324,3 +324,39 @@ There are three levels to think about:
writes inside the plugin. The journal mode is left at the default writes inside the plugin. The journal mode is left at the default
(`delete` for files, `memory` for `:memory:`), so concurrent reads (`delete` for files, `memory` for `:memory:`), so concurrent reads
work as expected. work as expected.
## What happens if the process dies mid-task
`better-sqlite3` opens with `synchronous=FULL` by default, which means
**every commit is fsynced to disk before the `sqlite_exec` call
returns**. Anything the model has already written is on the disk and
will be there on the next open, no matter how the process dies.
| Event | Already-committed data | Connections |
| ----- | ---------------------- | ----------- |
| Close the TUI / CLI window | safe | unchanged — the service is still running, the handle is still open |
| `opencode service stop` (SIGTERM) | safe | unclosed. Node exits without a `db.close()` call unless the host installs a handler. The OS reclaims the fds. |
| `kill -9` the service | safe | unclosed. The kernel reclaims the fds immediately. |
| Power loss / kernel panic | safe (last fsynced commit) | irrelevant — the process is gone. |
| `sqlite_open(":memory:")` and *anything* that ends the process | **lost** — `:memory:` lives only in process RAM | n/a |
Verified empirically: `INSERT … ; setInterval; kill -9` left the
three rows intact on a fresh re-open. Same result for `kill -TERM`
with and without a SIGTERM handler that calls `db.close()`.
**The only ways to lose data are:**
1. Using `":memory:"`. There is no on-disk copy.
2. Turning `synchronous` off with `PRAGMA synchronous = OFF;` — the
commit returns before the bytes hit the disk, so a crash after the
return but before the kernel writes can lose that statement. This
plugin does not do this for you, but `sqlite_exec` will run it if
asked.
3. Running on a network filesystem (NFS, SMB, sshfs). Both SQLite's
locking and POSIX fsync semantics are unreliable there.
**About the "leak fd" worry from the previous section:** that only
matters while the service is alive. As soon as the process exits, the
OS reclaims every fd it owned, including the database file and any
journal sidecar. A crash or hard kill is *less* leaky than a graceful
shutdown that the plugin forgets to clean up.