← All posts
Changelog context-bridge · 3 min read

The first switch into OpenCode

I described OpenCode's delivery path accurately enough to expose the bug, and did not notice. The first switch into OpenCode never delivered anything.

The bug wasn't in the code.

It was in the release notes.

While writing about the last release, I described OpenCode's delivery path like this:

context-bridge now writes the handoff directly into OpenCode's database when resuming a session.

That sentence is correct.

It also quietly explains why the first switch into OpenCode never worked.

...when resuming a session.

On a first switch, there is nothing to resume.

I had described the implementation accurately enough to expose the bug without noticing it.


context-bridge moves an active coding session from one terminal agent to another.

Not a pasted summary.

Not a fresh conversation.

The receiving agent continues inside its own native session with the decisions, the open questions and the current Git state already there.

Claude Code, Codex, Grok and Antigravity all expose a place where the bridge can inject a handoff.

OpenCode doesn't.

There is no hook.

There is no resumable prompt.

The bridge writes directly into OpenCode's SQLite database instead.

That works perfectly.

As long as a session already exists.


On the first switch, it doesn't.

Every message in OpenCode belongs to a session.

A message cannot exist on its own.

If there is no session row, there is nowhere to attach the handoff.

The write cannot happen.

The delta stays pending.

The launcher's recovery path couldn't help either.

Normally it waits for an OpenCode session to appear, then links it automatically.

But there was no session to discover, because OpenCode hadn't created one yet.

And the bridge couldn't write one, because writing wasn't its job.

Not a deadlock. A gap.

The handoff arrived at the one moment nothing existed to receive it.

OpenCode launched.

The bridge reported it as unlinked.

None of the transferred context appeared.


The important part is what didn't happen.

Nothing was lost.

A delivery is committed only after the destination has accepted it.

Not when a process starts.

That rule exists because an earlier Claude → Codex bug failed the other way.

Back then, spawning a process counted as delivery.

The delta was renamed .consumed.

The process failed.

The handoff disappeared.

There was nothing left to retry.

That bug changed the delivery model.

Since then, "started" and "delivered" have never meant the same thing.

Because of that decision, this bug delayed the handoff.

It never destroyed it.

I would rather ship a bridge that is occasionally late than one that silently loses work.


The fix is almost embarrassingly simple.

If there is no session...

Create one.

fabricateSession() inserts the session, the handoff message and its first part inside a single SQLite transaction.

The launcher resumes that session by id, links it, and from that point onward the flow is identical to every subsequent switch.


Two implementation details turned out to matter.

The project isn't guessed.

It is resolved from OpenCode's own database.

COALESCE(
  (SELECT id FROM project WHERE worktree = ?),
  'global'
)

If the worktree already belongs to a project, the fabricated session joins it.

Otherwise it falls back to the global project, exactly as OpenCode itself does.

Guessing either value would have placed half of all first sessions under the wrong project.

The session id isn't random either.

It is derived from the worktree and the delta.

If the command is rebuilt before delivery is committed, the same id is produced, the insert becomes a no-op, and no duplicate session appears.

A random identifier would quietly create another ghost session every time.


The implementation was reviewed twice.

First by OpenCode, to verify the database model.

Then by Codex, to review the code itself.

Finally, the bridge fabricated a session into a real OpenCode store, resumed it, linked it, and verified the result through OpenCode's own session list.

337 tests pass.

The first-switch regression is now covered.

The original bug was reintroduced deliberately.

The new test failed.

Then the fix went back.

The test passed.

That's the one I trust.


If you're on 0.12.0

0.12.0 was available for less than two days.

If you installed it and use OpenCode, update:

npm i -g @serdardb/context-bridge

Only the very first switch into OpenCode inside a project was affected.

Every other agent behaved correctly.

Every later OpenCode switch behaved correctly.

Only the first one had nowhere to land.