Skip to content

fix(session): create sessions on the first prompt, one tab per session - #542

Open
jensenojs wants to merge 11 commits into
sudo-tee:mainfrom
jensenojs:fix/draft-session-submit
Open

jensenojs wants to merge 11 commits into
sudo-tee:mainfrom
jensenojs:fix/draft-session-submit

Conversation

@jensenojs

@jensenojs jensenojs commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

TL;DR — opening the panel no longer creates a session. The first prompt does, and the session stays in the single tab that owns it.

                     ┌───────────────────────────────┐
   open / restart ──►│ snapshot  [session_id, …]     │
   tab+session    ◄──└───────────────────────────────┘
          ▲
          │ create
-         ├── "new session" asked
-         ├── the first prompt
+         └── the first prompt in that tab
+               ├─ agent resolved first, else "build"
+               └─ send once sync.session.state == "current"

Opening created a session so the panel had something to show. It carried no prompt, so nothing in the snapshot separated it from a session someone had worked in, and the next open restored it as "New session". Which write stored that id could not be recovered afterwards: writes recorded neither the ids nor a trigger, and a failed lookup did not name the missing id.

A tab now stays empty until a prompt arrives. send_message creates the session and waits for the session resource to be current before sending, because a submit against a resource still syncing is rejected.

A session belongs to exactly one tab. Asking for a session another tab already holds focuses that tab; binding it twice raises instead of quietly producing a second tab. The tab runtime owns that fact; store.active_session is a projection of the active tab.

On the write side, a tab without a session produces no snapshot entry, and every write logs the stored ids, the active index and its trigger. A snapshot written by an older version may already hold an empty session: it restores once, unfiltered, and the log names the write that stored it.

Also in this branch, separable if unwanted:

  • log destination resolved when logging, and six silent failures now report
  • v2 message ids containing : are rejected — the only change that can reject data the old code accepted; revert that commit alone if you disagree
  • two test waits widened for slow runners

Verified in an isolated instance: opening leaves no server session, the first prompt creates and sends one, restarting restores it. ./run_tests.sh passes.

@jensenojs jensenojs changed the title fix(session): materialize draft session on first prompt fix(session): stop persisting an empty session when the panel opens Oct 20, 2026
setup() configures logging.outfile and logging.level, but the module
captured both when it loaded. Anything requiring opencode.log before setup
runs — a plugin spec's top-level require, for instance — pinned the default
path and threshold, so the configured file stayed empty and debug lines were
dropped without a trace.

Read the path and the threshold per call.
Opening the panel created a server session so the panel had something to
show. That session carried no title and no tokens; nothing distinguished it
from one a user had worked in. The tab layout snapshot stored it all the
same, so the next open restored it and displayed "New session".

Leave the tab empty instead. messaging.send_message creates the session when
the first prompt arrives, waits until the session resource reports current,
and submits the mode, model, context and variant captured at submit time, so
a change made in another tab during that wait cannot redirect the prompt.

The snapshot stores only tabs that carry a session, and its write names the
stored ids, the active index and the trigger (exit, change <group>, group
switch, manual); a snapshot lookup that fails names the session id it could
not read.
The "New session" report could not be traced: the snapshot lookup that
failed and the write that stored the empty session both returned without a
word. A logged outcome is the difference between a guess and a lead.

Cover the boundaries that decide the panel's content: observation resource
loads, stream failures and event routing; contract checks; port mapping
loads; session part lookups; store listeners; history reads and writes.
The v2 service sends text, reasoning and tool parts without a stable part
id, so the plugin synthesizes one as owner ":" kind ":" key and only ever
compares it. A colon inside the owner lets two different coordinates produce
the same string, which renders two parts as one.

Ids without a colon is current server behaviour, but that is an external
contract; check it where the id is synthesized instead of assuming it.
The empty multi-select answer needs a second Enter and the test waited 200ms
for it. That is enough locally and not on the macOS runners.
@jensenojs
jensenojs force-pushed the fix/draft-session-submit branch from 24eae9d to 8d29476 Compare October 20, 2026 06:13
@jensenojs jensenojs changed the title fix(session): stop persisting an empty session when the panel opens fix(session): persist only sessions a prompt has used Oct 20, 2026
@jensenojs
jensenojs marked this pull request as ready for review October 20, 2026 06:56
@jensenojs
jensenojs requested a review from sudo-tee October 20, 2026 06:58
@sudo-tee

Copy link
Copy Markdown
Owner

I like the idea of not creating a real session until a prompt is sent. I have so many empty sessions. 😅. I will play with this MR today.

Thanks

@sudo-tee

Copy link
Copy Markdown
Owner

Maybe I did not understand correctly the behavior,

but I tought that creating a new session will not persist it.

Open the panel create a new session leader oN but when I quit and reopen the new session is still in the panel.

And the new session is visible in the session switcher.

@jensenojs jensenojs changed the title fix(session): persist only sessions a prompt has used fix(session): persist only sessions that were asked for or used Oct 20, 2026
@jensenojs

Copy link
Copy Markdown
Collaborator Author

Right — opening in a directory with nothing to restore no longer creates a session; the first prompt does.

The new-session action is separate: it still creates one right away(After all, it's an intention),

@jensenojs

Copy link
Copy Markdown
Collaborator Author

However, accidental triggers are indeed possible, so we might as well adjust it in this PR. This would also be more consistent with TUI behavior. This is actually the first time I've used this shortcut in opencode.nvim. I'll probably have to wait until I get home from work to handle it, though.

@sudo-tee

Copy link
Copy Markdown
Owner

Perfect no problems, I also think we should match the TUI on this aspect.

@jensenojs

Copy link
Copy Markdown
Collaborator Author

There’s an issue with the latest commit. I’ll deal with it when I get home......

@jensenojs

Copy link
Copy Markdown
Collaborator Author

I don't usually use this tab feature, but I've noticed that after opening it with N, mashing 1/2 randomly makes it quite easy for two tabs to point to the same session, which is almost never what we want.

`<c-x>N` and switching to a tab without a session both went through
`M.open`, which resumed the last workspace session. The resume ran through
`set_active`, which bound that session to whichever tab was active, so the
tab that already owned it ended up duplicated.

Sessions are now owned by the tab: `runtime.active_session` is the source
of truth, `store.active_session` only projects the active tab, and asking
for a session another tab owns activates that tab instead of creating a
second one. `verify_unique_session_tabs` reports a violation loudly at
every point that mutates the registry.

An empty tab stays empty: `M.open({ draft = true })` skips the resume
branch, and the first prompt creates the session.
@jensenojs

Copy link
Copy Markdown
Collaborator Author

@sudo-tee plz try and feel free to adjust what you need, i need to go to bad (。・ω・。)ノ♡

@jensenojs jensenojs changed the title fix(session): persist only sessions that were asked for or used fix(session): create sessions on the first prompt, one tab per session Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants