opencode-session-store
v1.2.1
Published
Persist all OpenCode session data to .opencode/sessions/ as structured JSON files
Maintainers
Readme
opencode-session-store
Persist all OpenCode session data to .opencode/sessions/ as structured JSON files.
Captures every message, tool call, sub-agent interaction, permission prompt, and model error — so nothing is lost when sessions are compacted or the context window fills up.
Installation
// .opencode/opencode.json
{
"plugin": ["opencode-session-store"]
}On next startup, OpenCode will automatically install and load the plugin.
What It Captures
| Data | Source |
|---|---|
| User prompts | client.session.messages() — diff-fetched on every event |
| Assistant replies | same — role: "assistant" with text + reasoning + tool parts |
| Tool calls & results | type: "tool" parts with input/output/error state |
| Sub-agent tasks | type: "subtask" parts with agent name and prompt |
| File attachments | type: "file" parts (images, documents, etc.) |
| Permission dialogs | permission.updated / permission.replied events |
| Model/provider errors | message.error on turns + session.error events |
| Compaction markers | session.compacted event — timestamp logged |
| Sub-agent hierarchy | session.created with parentID — parent→child links stored |
Storage Format
One file per session in .opencode/sessions/{sessionID}.json:
{
"_id": "ses_abc123",
"title": "Fix auth bug",
"startedAt": "2026-07-29T10:28:32.000Z",
"updatedAt": "2026-07-29T10:46:36.000Z",
"compactedAt": null,
"parentId": null,
"subSessions": ["ses_child456"],
"turns": [
{
"role": "user",
"messageId": "msg_user_1",
"timestamp": "2026-07-29T10:28:32.000Z",
"parts": [{ "type": "text", "text": "Fix the auth bug in login.ts" }]
},
{
"role": "assistant",
"messageId": "msg_asst_1",
"timestamp": "2026-07-29T10:28:35.000Z",
"parts": [
{ "type": "thinking", "text": "Let me look at the login code..." },
{ "type": "text", "text": "I'll read the file first." },
{
"type": "tool",
"toolName": "read",
"toolInput": { "filePath": "src/login.ts" },
"toolOutput": "function login...",
"isError": false
}
]
},
{
"role": "assistant",
"messageId": "msg_asst_2",
"timestamp": "2026-07-29T10:28:45.000Z",
"parts": [
{
"type": "subtask",
"agent": "general",
"prompt": "Find similar auth patterns",
"description": "Search codebase for auth patterns"
}
]
},
{
"role": "assistant",
"messageId": "msg_asst_3",
"timestamp": "2026-07-29T10:29:00.000Z",
"parts": [],
"error": {
"name": "APIError",
"message": "The latest version of this model is only available hosted in China and requires explicit opt in",
"statusCode": 403,
"isRetryable": false
}
}
],
"permissions": [
{
"type": "asked",
"timestamp": "2026-07-29T10:28:50.000Z",
"permission": "bash",
"command": "rm -rf /tmp/test",
"question": "Run this command?"
},
{
"type": "replied",
"timestamp": "2026-07-29T10:28:51.000Z",
"permission": "bash",
"allowed": false
}
],
"errors": [
{
"timestamp": "2026-07-29T10:29:00.000Z",
"name": "APIError",
"message": "The latest version of this model is only available hosted in China and requires explicit opt in",
"statusCode": 403,
"sessionID": "ses_abc123"
}
]
}Index File
A manifest of all sessions is kept at .opencode/sessions/index.json, sorted by
startedAt ascending. This allows listing and ordering sessions chronologically
without scanning the directory or loading every session file.
{
"sessions": [
{
"id": "ses_abc123",
"title": "Fix auth bug",
"startedAt": "2026-07-29T10:28:32.000Z",
"updatedAt": "2026-07-29T10:46:36.000Z",
"parentId": null
}
]
}The index is kept in sync automatically — entries are added on session creation and updated whenever session metadata changes.
How It Works
- On
session.created: creates the session file with metadata - On
session.updated/message.*: schedules a debounced sync (500ms) - On
session.compacted: marks compaction timestamp, does a full sync - On
permission.*: records permission prompts and user responses - On
tool.execute.after: triggers a sync to catch new tool results - On
session.error: records model/provider failures to the session-levelerrorsarray
Each sync calls client.session.messages() and diffs against known message IDs, appending only new turns. Assistant turns that fail with an error are kept even when they have no content parts; the error is stored on the turn.
Events Handled
session.created/session.updated/session.deleted/session.compacted/session.errormessage.updated/message.part.updated/message.removedpermission.updated/permission.repliedtool.execute.after
Robustness
- Session files are parsed leniently: trailing NUL bytes and stray control characters (corruption) are stripped before parsing, so a damaged file does not break the plugin.
- If a session file is missing or unreadable, the next sync rebuilds it from the authoritative
session.messages()API data instead of failing silently. - Per-session syncs are serialized so concurrent events cannot interleave read-modify-write cycles.
- On startup the plugin scans existing session files and reports any corruption.
Diagnostics
Sync failures and detected corruption are written to .opencode/sessions/sync-errors.log (one line per event, timestamped). The plugin never prints to stdout.
