Skip to content

feat(core): add storage.sessions config to delete old sessions - #57882

Open
opencode-agent[bot] wants to merge 1 commit into
devfrom
session-retention
Open

opencode-agent[bot] wants to merge 1 commit into
devfrom
session-retention

Conversation

@opencode-agent

@opencode-agent opencode-agent Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Issue for this PR

Part of #16101

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Adds a storage config group with one field, storage.sessions. It takes a duration and is unset by default, so sessions are kept forever unless you opt in. The group can hold other local-data cleanup later (e.g. storage.snapshots).

{ "storage": { "sessions": "30 days" } }

SessionExpiry (packages/core/src/session/expiry.ts, new) runs an hourly global sweep next to the project archive sweep:

  • Config read: same approach as the CLI updater's update. It reads config.json, opencode.json, and opencode.jsonc from the global config directory, parses JSONC, and uses storage.sessions from the last file that sets it. Project config is never read, so a project can't delete other projects' sessions. As with update, a project-level value is silently ignored, and system/MDM-managed config and {env:...} substitution don't apply.
  • Deletion: top-level sessions whose time_updated is older than the cutoff are removed through Session.remove, so children, events and projections go the same way as a manual delete. Suspended sessions (owned by restart recovery) and sessions with an active execution or running background job are skipped. Non-finite or zero durations are ignored with a warning.

Deleting rows doesn't shrink opencode.db by itself; reclaiming pages is separate (#56365).

How did you verify your code works?

  • bun test test/session-expiry.test.ts: deletes an expired top-level session and its child, keeps a recent one and a suspended one, reads the value from a temp global config dir (unset → keep), and decodes JSONC edge cases
  • bun test test/config/normalization.test.ts with a new storage.sessions case
  • server session-create, config, workerd tests with the node in the route graph
  • tsc -b for core (including tests) and server; regenerated protocol/www OpenAPI and client types

Screenshots / recordings

N/A

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Requested by: @jlongster (James Long via Slack)

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This works as described. I ran serve from source with isolated config and data dirs and moved two sessions' time_updated back 40 days. With session_retention: "30 days" only in the project's opencode.json, nothing was deleted. With it in the global opencode.json, startup deleted the two old sessions and kept the recent one. The new tests and the normalization test pass, and type checks pass for core (with tests) and server.

Public API: adds an optional session_retention config key (a duration string) to Config.Info, plus the matching OpenAPI schema and generated client type. Nothing is removed or changed and the default is unset, so this is backwards compatible. The key is only read from global, system and managed config; if it is set in a project config it is accepted but ignored.

One thing worth fixing: the sweep rebuilds a full config service every hour just to read one key (see inline comment).

Related: #55293

Comment thread packages/core/src/session/retention.ts Outdated
const SWEEP_INTERVAL = Duration.hours(1)

/** Reads `session_retention` from global, system, and managed config. Project config never applies. */
export const configured = Effect.fn("SessionRetention.configured")(function* () {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

configured() builds a new Config.layer on every hourly sweep, even when session_retention is unset. That layer does more than read files: it sets up watchers on the config roots (in my run they started and stopped on /etc and /root at each sweep), resolves well-known config again (an HTTP fetch of remote_config for organisations that use one), and forks reload and refresh fibers that are thrown away right after. Every user would pay this cost hourly just to read one key. Could this read the global, system and managed documents in a lighter way (for example ConfigDiscovery plus parsing just those files), or keep one long-lived global-scoped config instead of rebuilding it each sweep?

@opencode-agent opencode-agent Bot changed the title feat(core): add session_retention config to delete old sessions feat(core): add storage.sessions config to delete old sessions Oct 21, 2026

This branch has not been deployed

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant