{"slug": "my-opencode-database-was-mostly-empty-space", "title": "My OpenCode Database Was Mostly Empty Space", "summary": "A developer discovered that their OpenCode database file had grown to 1.3 GB, but SQLite analysis revealed that 766 MB of that space was a freelist of reusable pages, not live data. The engineer explains that SQLite does not automatically shrink the file when rows are deleted, and that agent sessions produce large amounts of bursty storage due to tool calls, shell output, and session metadata. They recommend using PRAGMA commands to measure freelist vs. live data rather than relying on file size alone.", "body_md": "OpenCode keeps a lot of useful history.\n\nThat is one of the reasons I like it. A coding-agent session is not just a chat transcript. It is a record of what I asked, what the agent did, which tools ran, what failed, what got fixed, and what context existed at the time. I have argued before that the agent session is becoming a log file. I still believe that.\n\nThen I noticed the log file was getting large.\n\nOn one machine, `~/.local/share/opencode/opencode.db`\n\nhad grown past a gigabyte. That was not shocking by itself. I use OpenCode heavily. Long sessions produce many messages, tool results, shell outputs, file reads, and state transitions. A local database growing over time is expected.\n\nWhat was surprising was what SQLite reported after looking inside the file: much of it was not live data anymore.\n\nIt was empty space.\n\nMost developers learn an intuitive model of storage that is only partly true.\n\nIf I delete rows from a database, I expect the database file to get smaller. If I archive old sessions, compact history, or remove records, I expect disk usage to fall. At the application level, that feels right: fewer records should mean fewer bytes.\n\nSQLite does not normally work that way.\n\nWhen rows are deleted, SQLite can reuse the freed pages for future writes, but it does not necessarily return those pages to the filesystem. The file can stay the same size while containing a growing internal pool of reusable pages. That pool is the freelist.\n\nThe important distinction is:\n\n```\ndatabase file size != live data size\n```\n\nThe file can be 1.3 GB while the live data is only 525 MB. The remaining 766 MB can be pages SQLite is keeping around for reuse. From SQLite's perspective, that space is available. From the filesystem's perspective, it is still occupied.\n\nThat is not corruption. It is not necessarily a bug in the data model. It is how SQLite behaves unless the database is configured and vacuumed in a way that returns free pages to the OS.\n\nFor a normal application database, this may not matter. For a local agent runtime that stores every tool-heavy session in one file, it becomes noticeable.\n\nAgent sessions are unusually good at producing large, bursty storage.\n\nA normal chat app stores messages. A coding agent stores messages plus tool calls, tool results, shell output, file contents, structured parts, todos, events, and session metadata. A single task can contain a surprising amount of durable state.\n\nOpenCode also has session compaction. Compaction is useful for model context management, but it is easy to confuse three different layers:\n\n| Layer | What changes |\n|---|---|\n| Model-visible context | Older conversation can be summarized behind a checkpoint. |\n| Durable history | Original rows can still exist in the database. |\n| SQLite file layout | Deleted or obsolete pages may remain inside the file as freelist space. |\n\nCompaction helps the model continue when context gets too large. It does not mean the database file shrinks. Even when rows are removed by some maintenance path, SQLite may keep the freed pages in the same file.\n\nThis is the kind of problem that is easy to misdiagnose if you only look at `du -h opencode.db`\n\n. A large file does not tell you how much live data exists. It only tells you how much disk the file currently occupies.\n\nThe first tool I wanted was not a cleaner. It was a measurement.\n\nSQLite already exposes the numbers needed to understand the problem:\n\n```\nPRAGMA page_size;\nPRAGMA page_count;\nPRAGMA freelist_count;\n```\n\nFrom those values, the diagnosis is simple:\n\n```\nfreelist_bytes = freelist_count * page_size\nlive_bytes = (page_count - freelist_count) * page_size\nfreelist_pct = freelist_count / page_count\n```\n\nThat gives a more useful report than raw file size:\n\n```\nOpenCode Database Health Report\n----------------------------------------------------------\n  File size:     1.3 GB\n  Page size:     4.0 KB\n  Journal mode:  wal\n  Auto-vacuum:   OFF (NONE)\n\nStorage\n----------------------------------------------------------\n  Live data:     525 MB\n  Freelist:      766 MB  (56% of file)\n  -> VACUUM would reclaim ~766 MB\n  -> Estimated result:  ~525 MB\n```\n\nThis changes the conversation. The question is no longer \"why is the database 1.3 GB?\" The better question is \"how much of this file is still live data?\"\n\nIf the freelist is small, there is nothing urgent to do. If half the file is freelist pages, a cleanup can reclaim real disk space.\n\nThat became [ ocdbc](https://github.com/chncaesar/opencode-db-clean): OpenCode Database Cleaner.\n\nThe dangerous version of this tool would delete sessions.\n\nThat is not what I wanted. I did not want a policy engine deciding which conversations were old, stale, low-value, or safe to remove. Agent sessions are evidence. They contain exactly the kind of messy operational detail I often need later: commands, errors, constraints, design decisions, failed attempts, and verification output.\n\nSo `ocdbc`\n\nhas a narrower job.\n\nIt does two things:\n\n```\nocdbc analyze\nocdbc vacuum\n```\n\n`analyze`\n\nis read-only. It reports database size, live data, freelist space, table sizes, session age distribution, and the largest messages.\n\n`vacuum`\n\ndoes not decide what history should exist. It asks SQLite to rebuild the database file so unused pages can be returned to the filesystem. The goal is physical cleanup, not semantic deletion.\n\nThat boundary matters. A tool that deletes history needs product policy. A tool that reports freelist space and runs a safe SQLite maintenance sequence can stay much smaller.\n\n`VACUUM`\n\nsounds boring until you remember what file it is operating on.\n\nThis is the local database for an agent runtime. If OpenCode is running while the database is being rebuilt, I do not want to discover edge cases by corrupting my own session history. If the write-ahead log has committed data that has not been checkpointed into the main database file, I do not want to back up an incomplete view. If the database is already corrupt, I do not want the cleanup tool to make the failure harder to reason about.\n\nSo `ocdbc vacuum`\n\nis intentionally conservative.\n\nThe sequence is:\n\n`fuser`\n\nshows OpenCode still has the database open.`PRAGMA integrity_check`\n\nbefore making changes.`auto_vacuum = INCREMENTAL`\n\nso future free pages can be reclaimed incrementally.`VACUUM`\n\nto rebuild the file.`VACUUM`\n\ncan reset journal mode.The order is the point.\n\nCheckpoint before backup means the `.db`\n\nfile contains committed WAL data before it is copied. Verifying the backup means the fallback is not just a file that happened to exist. Refusing to run while another process has the database open prevents a maintenance tool from racing the application it is trying to help.\n\nThere are override flags, but they are explicit:\n\n```\nocdbc vacuum --force\nocdbc vacuum --skip-fuser --force\nocdbc vacuum --no-backup --force\n```\n\nThe default path optimizes for not losing data.\n\nThe whole package is a single Python module with no runtime dependencies. Installation is intentionally uninteresting:\n\n```\npip install ocdbc\n```\n\nThen:\n\n```\nocdbc analyze\n```\n\nIf the report shows significant freelist bloat, close OpenCode and run:\n\n```\nocdbc vacuum\n```\n\nThat is it.\n\nThe tool does not need a daemon. It does not need to understand model context. It does not need to parse sessions or judge which messages matter. It only needs to expose the maintenance operation I wanted OpenCode users to have available when the database file becomes misleadingly large.\n\nThat is the pattern I keep coming back to with agent tooling. The best tool is often not the most intelligent one. It is the one that gives the agent or developer a missing primitive with a tight safety boundary.\n\nFor readiness checks, that primitive was `wait_for`\n\n: poll a URL, port, or command until the condition is actually true.\n\nFor session review, that primitive was `session_reflection`\n\n: treat recent sessions as evidence instead of disposable scrollback.\n\nFor OpenCode database bloat, the primitive is `ocdbc`\n\n: show the difference between live data and empty pages, then run the boring SQLite cleanup correctly.\n\nAI-assisted development creates new kinds of local infrastructure.\n\nThe agent runtime is not just a prompt window. It has tools, permissions, sessions, transcripts, databases, plugins, logs, background state, and failure modes. Once that runtime becomes part of daily work, it needs the same boring maintenance primitives every other developer toolchain needs.\n\nSometimes the fix is not a better model.\n\nSometimes it is knowing that your 1.3 GB database contains 766 MB of empty pages, closing the app, checkpointing the WAL, verifying a backup, running `VACUUM`\n\n, and getting your disk space back.\n\nThat is not glamorous.\n\nIt is exactly the kind of boring tool I want more of.", "url": "https://wpnews.pro/news/my-opencode-database-was-mostly-empty-space", "canonical_source": "https://dev.to/antonio_zhu_e726fd856cd86/my-opencode-database-was-mostly-empty-space-1c49", "published_at": "2026-07-22 07:47:16+00:00", "updated_at": "2026-07-22 08:01:54.380908+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["OpenCode", "SQLite"], "alternates": {"html": "https://wpnews.pro/news/my-opencode-database-was-mostly-empty-space", "markdown": "https://wpnews.pro/news/my-opencode-database-was-mostly-empty-space.md", "text": "https://wpnews.pro/news/my-opencode-database-was-mostly-empty-space.txt", "jsonld": "https://wpnews.pro/news/my-opencode-database-was-mostly-empty-space.jsonld"}}