Skip to content

feat(opencode): OpenCode ephemeral sessions - #43656

Closed
5herlocked wants to merge 10 commits into
anomalyco:devfrom
5herlocked:ephemeral-run
Closed

5herlocked wants to merge 10 commits into
anomalyco:devfrom
5herlocked:ephemeral-run

Conversation

@5herlocked

@5herlocked 5herlocked commented Aug 20, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #4489
Potentially enables #40863

Type of change

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

What does this PR do?

Adds ephemeral sessions as a core concept to OpenCode server, then exposes the functionality through the --ephemeral flag.

Server owns the ephemeral sessions and cleans them up when (now() - time_updated) >= 24h (to permit long running ephemeral tasks). We reuse time_updated as it's the codebases' indicator for last observed activity.

There are also client side updates to promptly delete ephemeral sessions as soon as they end / are terminated through SIGINT / SIGTERM. Note - SIGKILL leaves the session in the DB for the 24h period as a way to recover killed sessions assuming they need to be recovered.

How did you verify your code works?

Various unit tests + functional testing by compiling the branch, and testing 10 ephemeral sessions to make sure they don't show up in the UI + the DB (assuming they were terminated properly). Tested against Bedrock to ensure long running sessions are still cleaned up when SIGINT/SIGTERM but live on when SIGKILL

End-to-end (compiled from source, isolated data dir, real provider: Bedrock us-east-1 / claude-sonnet-5)

Scenario Expected Observed
Normal run (no flag) Session persists, ephemeral=0 persists with real token usage
Ephemeral run, natural termination Exit 0, session deleted exit 0, real model output, session gone, zero orphans across session/message/part/session_input/event
Repeated ephemeral runs (7x) No leftovers, no flakes 7/7 clean
Mid-run visibility Row exists (ephemeral=1) but hidden from session list present in DB, absent from list
SIGINT mid-generation (interrupted at 12s of a ~20s run) Exit 130, session deleted exit 130, no orphans
kill -9 mid-run Exit 137, orphan remains (uncatchable — by design) orphan present, still hidden from lists
Next startup after orphan goes stale (>24h, simulated by backdating time_updated) Sweep deletes orphan + cascade orphan swept, zero orphaned rows, normal sessions untouched
Resume orphan by ID (--session <id>) History intact and loadable loads (adoption semantics left as open question below)

Screenshots / recordings

If this is a UI change, please include a screenshot or recording.

Checklist

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

If you do not follow this template your PR will be automatically rejected.

I have used LLM generated code in this PR, and have personally reviewed all of it.

There is a more philosophical question that the SIGKILL recovery raises -- if a use recovers a session (through opencode --session <ephemeral_session_id>) that was ephemeral and then killed, should the ephemeral flag persist (causing sweep >= 24h after time_updated)?
And do we then create a flag under session list that lets us list ephemeral sessions that haven't been sweeped yet.

@5herlocked
5herlocked marked this pull request as draft August 20, 2026 15:22
@5herlocked
5herlocked marked this pull request as ready for review August 20, 2026 15:40
@5herlocked
5herlocked marked this pull request as draft August 20, 2026 21:01
@5herlocked
5herlocked marked this pull request as ready for review August 20, 2026 21:12
@Enough1122

Copy link
Copy Markdown

AI code review — automated review for reference; please use your judgment.

  • packages/core/src/session/projector.ts:102-110 — the liveness heartbeat only fires in applyUsage, so an ephemeral session whose run sits idle-but-alive for a long stretch (slow tool, pending permission ask, human-in-the-loop pause) never refreshes time_updated and can be reaped by the startup sweep while its owning process is still running. Either emit heartbeats from non-usage activity too, or make sure the documented staleness window comfortably exceeds the maximum legitimate idle time.
  • packages/opencode/src/cli/cmd/run.ts:465-482 — removeEphemeral() has no timeout; a hung session.delete HTTP call will block every completion path (return done()) and the SIGINT/SIGTERM handlers, leaving the CLI wedged at exit exactly when it is trying to clean up. Wrap the fan-out in a short timeout (e.g. Promise.race with 5-10s) — the server-side sweep already covers stragglers.
  • packages/opencode/src/share/session.ts:41-43 — create() skips auto-share for ephemeral sessions but says nothing about child sessions: creating a sub-session under an ephemeral parent still persists the child, which then outlives the deleted parent with a dangling parentID. Either inherit ephemeral to children or refuse child creation under an ephemeral parent.
  • packages/core/src/session/projector.ts:108 — nit: embedding Date.now() directly in the SQL CASE bypasses the app's date abstraction used everywhere else (DateTime.makeUnsafe(...) on read); routing it through the shared helper keeps clock handling uniform and makes time-based tests easier to fake.
  • packages/opencode/test/session/session.test.ts / core/test/session-projector.test.ts — good coverage of flag plumbing and decoding; the two riskiest behaviors (startup sweep reaping abandoned sessions, heartbeat keeping live ones) appear untested — a pair of focused tests there would lock in the semantics this design leans on.

Address review feedback on anomalyco#43656:
- Process-owned heartbeat bumps time_updated for ephemeral sessions every
  10 minutes while the creating process lives, so idle-but-alive runs
  (pending permission asks, open ephemeral TUIs) are never reaped by the
  startup sweep. Beats stop with the process, preserving sweep semantics.
- Exit-time session deletion is wrapped in a 5s timeout in both run and
  tui paths so a hung delete cannot wedge the process at exit; the sweep
  covers stragglers.
- Usage-projection heartbeat now stamps event time instead of wall clock,
  keeping projection replay deterministic.
@github-actions

Copy link
Copy Markdown
Contributor

Automated PR Cleanup

Thank you for contributing to opencode.

Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions.

This PR was closed because it matched the following cleanup criteria:

  • The PR was created more than 1 month ago
  • The PR had fewer than 2 positive reactions
  • Positive reactions are counted as thumbs-up, heart, celebration, or rocket reactions on the PR

PRs created within the last month are not affected by this cleanup.

If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate.

Thanks again for taking the time to contribute.

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.

[FEATURE]: Ephemeral one‑off sessions for opencode run (happy to implement)

2 participants