Skip to content

storage: base64 image tool outputs dominate opencode.db (538 MB of 587 MB) #58436

Open

Description

@MercuryOoO

Summary

On OpenCode v2.0.20, opencode.db grew to 587 MB while holding only 3.7k messages. 538 MB (92%) of the file was base64 image data embedded directly in session_message.data from image tool results (content[].uri = "data:image/png;base64,..."). Deleting the offending session with the official CLI freed 428 MB of pages but did not shrink the file; a manual VACUUM reduced it to 158 MB.

Environment

  • opencode version: 2.0.20 (@opencode/cli)
  • OS: Windows 11 26H2, build 26300.9711, x64 (reported by CurrentVersion as "Windows 10 Pro")
  • Terminal: Windows Terminal, TERM=xterm-256color, TERM_PROGRAM unset
  • Shell: PowerShell 7.6.6 (pwsh)
  • Install/channel: npm global install of @opencode/cli@2.0.20
  • Active plugins: none (global opencode.jsonc has no plugin/plugins entries)

Reproduction

  1. In a single session, use the image tool repeatedly (about 50 calls, 4 images per assistant turn).
  2. Observe each tool result stored in the session history:
    { "type": "file", "uri": "data:image/png;base64,<1–5 MB>", "mime": "image/png" }
  3. After a ~6 hour session: 550 messages, 421 MB of session_message.data, DB file 587 MB.
  4. Delete the session officially: opencode session delete <sessionID> → succeeds, 428.6 MB becomes freelist pages, file size unchanged at 587 MB.
  5. Run VACUUM externally → 157.5 MB, PRAGMA quick_check = ok.

Expected Behavior

  • Media produced by tools should be stored as files (or at minimum as BLOBs) and referenced from the message, not inlined as base64 inside JSON history.
  • Disk usage should be proportional to text conversation size; 3.7k messages should not approach 600 MB.
  • After deleting a session, the reclaimed space should be returned to the filesystem (automatic VACUUM/auto_vacuum=INCREMENTAL), or a documented maintenance command should exist.

Actual Behavior

Measured read-only (file:...?mode=ro, no writes):

Table / column Size Rows
session_message.data 567.0 MB 3,697
kv.value 6.3 MB 2
instruction_blob 0.24 MB 65

Message type breakdown:

type size rows
assistant 562.4 MB 2,982
compaction 2.2 MB 29
user 1.3 MB 210
system 1.0 MB 93

Image payload:

  • 298 embedded data:image/* URIs = 538.2 MB of base64 text (PNG 524.6 MB, JPEG 13.6 MB)
  • Largest single message: 14.93 MB, containing four image tool results of 3.2–4.8 MB each
  • One session accounted for 199 images / 418.3 MB
  • After deleting that session, 99 images / 119.9 MB remain in other sessions

Database facts: page_size=4096, page_count=150304, freelist_count=1597 before deletion (only 6.2 MB free), auto_vacuum=0, WAL mode. After session delete, freelist_count grew to ~104k pages (428.6 MB) but the file stayed at 587 MB.

Base64 inflates the binary payload by ~33% (403.6 MB of PNG/JPEG → 538.2 MB of text), and the same data is also materialized as a JSON string for every read of the message.

Additional Context

Workaround used, in this order:

opencode session delete ses_...
# file unchanged: 587 MB, 428.6 MB freelist
python vacuum.py   # PRAGMA wal_checkpoint(TRUNCATE); VACUUM; quick_check
# 587 MB -> 157.5 MB, quick_check ok, ~4.6 s

Suggestions:

  1. Store file tool outputs in the existing storage layer (content-addressed files), keeping only a path/hash in the message JSON.
  2. Add a migration/maintenance path that strips existing base64 payloads from history (text-only history stays readable).
  3. Enable auto_vacuum=INCREMENTAL for new databases, and/or add opencode db vacuum (with a --dry-run) so users do not need external SQLite tooling.
  4. Consider deduplicating identical images (the same generated image can appear in multiple messages/retries).

Related reports: #58124 (DB always growing), #55282 (687 MB DB, 99.5% free pages in another store), #56058 (RSS growth with image attachments), PRs #56365 (reclaim free pages) and #57882 (inactive session expiry). This report adds a distinct payload source: image tool outputs inlined as base64 in session_message.data, which is 92% of the file in this case.

Activity

  1. opencode-agent commented on Oct 23, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed measurements, @MercuryOoO. As I understand it, you're asking for two things: tool-produced media (such as image tool results) should be stored outside session_message.data and referenced from the message instead of inlined as base64, and the database should give freed space back to the filesystem, either through incremental auto-vacuum or a documented vacuum command. On current v2, media is stored inline and nothing vacuums the database, so this is a storage improvement rather than a regression. Maintainers will consider it.

    Related work you may want to follow or link to:

    Until then, your workaround (opencode session delete followed by an offline VACUUM with the service stopped) is the way to reclaim the space.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions