1
0
Fork 0
forked from dries/ocman

fix(opencode): handle duplicate OpenCode servers #4

Closed
peter wants to merge 4 commits from codex/opencode-port-affinity into main
Owner

Summary

  • Keep each OpenCode session's live traffic on one selected port, so session-detail fetches, composer sends, model/info fetches, and SSE traffic do not bounce between duplicate same-directory OpenCode servers.
  • Detect when multiple OpenCode servers are running for the same project directory and show a non-blocking warning in the session UI.
  • Cache duplicate-server discovery under the port-discovery TTL, and clear that cache through the existing port-cache invalidation path, so the warning does not run a fresh lsof scan for every session-detail response.
  • Clear stale port affinity when the selected port stops working, retry sends after transport failures, and only pin SSE traffic after OpenCode returns a valid 200 text/event-stream response.
  • Add regression coverage for duplicate-server discovery, duplicate-warning discovery caching, port affinity, stale pinned-port recovery, and the duplicate-server warning UI.

Actual issue observed

After fixing tmux launch and fresh port discovery, we were able to create a session, but live behavior still looked wrong when multiple OpenCode servers were running for the same repository directory.

The observed symptom was that a session appeared to keep working continuously, and the answer only visibly settled after pressing Escape. While debugging, we also observed multiple opencode servers rooted at the same project directory.

That made the next problem different from "can ocman find an OpenCode port at all?": ocman could find a port, but the directory -> port mapping was ambiguous when more than one matching server existed.

What we observed while debugging

  • Multiple OpenCode servers can exist for the same project directory, especially after repeated manual/ocman launches while testing session startup.
  • OpenCode port discovery is keyed by directory, so duplicate servers for one directory are ambiguous.
  • The UI has several live paths for a session: session-detail REST fetches, /event SSE proxying, composer sends, shell/command operations, model/info fetches, etc.
  • Before this patch, those paths resolved the directory -> port independently, so they could disagree if discovery returned a different server later.
  • Splitting a single session's live traffic across duplicate servers can produce confusing UI state, because the event stream and write path are no longer guaranteed to observe the same live process.

What we inferred / assumed

  • We inferred the confusing live behavior was caused by session traffic moving between duplicate OpenCode servers for the same directory.
  • We assumed that once ocman has selected a live port for a session, consistency is more important than repeatedly rediscovering a potentially different same-directory server.
  • We assumed session affinity should be in-memory and best-effort: it improves live routing while ocman is running, without introducing new persistent state.
  • We assumed stale affinity should be cleared when the chosen port clearly stops working, so a later lookup can rediscover a replacement server.
  • We assumed users should be warned when duplicate servers exist, because affinity makes the current session more stable but does not remove the underlying ambiguity.

Important caveats / implications

  • This does not prevent duplicate OpenCode servers from existing. It makes ocman more tolerant and makes the duplicate-server state visible.
  • The first selected port can still be arbitrary when several servers advertise the same directory. This PR is about consistency after selection, not about proving which duplicate server is "best".
  • Affinity is process-local and in-memory. Restarting ocman clears it, after which sessions are pinned again as live traffic is resolved.
  • Affinity is keyed by session ID, not process ID. If an OpenCode process dies and another server takes over, ocman recovers through failed fetch/connect/send paths and fresh discovery.
  • The warning is non-blocking. It tells the user duplicate servers may make live data ambiguous, but it does not choose or terminate servers for them.
  • Duplicate-server warning discovery is TTL-cached with the rest of port discovery. A warning can lag reality briefly, and launch/restart paths invalidate the cache when ocman knows the process set may have changed.
  • The send retry path avoids retrying typed OpenCode 4xx errors so real upstream rejections, such as validation/model errors, are not hidden by a second send attempt.

Stack

Base: codex/frontend-test-lint-hygiene (#5).

Validation

Run on codex/opencode-port-affinity after folding the warning branch into this PR and amending the warning discovery cache:

  • go test ./internal/platforms/opencode ./internal/server — passed.
  • cd frontend && pnpm lint — passed.
  • make test — passed (105 frontend test files, 1277 frontend tests).
  • make lint — still fails at the existing base-branch host-helper check in internal/server/loop_engine.go.

GitNexus:

  • Refreshed the stale index with npx gitnexus analyze before the cache edit.
  • Impact analysis for the mapped port-discovery symbol reported LOW risk.
  • npx gitnexus detect-changes mapped the final patch to port-discovery cache/invalidation symbols and the tmux launch/restart flows, which is the expected affected area for this change.
## Summary - Keep each OpenCode session's live traffic on one selected port, so session-detail fetches, composer sends, model/info fetches, and SSE traffic do not bounce between duplicate same-directory OpenCode servers. - Detect when multiple OpenCode servers are running for the same project directory and show a non-blocking warning in the session UI. - Cache duplicate-server discovery under the port-discovery TTL, and clear that cache through the existing port-cache invalidation path, so the warning does not run a fresh `lsof` scan for every session-detail response. - Clear stale port affinity when the selected port stops working, retry sends after transport failures, and only pin SSE traffic after OpenCode returns a valid `200 text/event-stream` response. - Add regression coverage for duplicate-server discovery, duplicate-warning discovery caching, port affinity, stale pinned-port recovery, and the duplicate-server warning UI. ## Actual issue observed After fixing tmux launch and fresh port discovery, we were able to create a session, but live behavior still looked wrong when multiple OpenCode servers were running for the same repository directory. The observed symptom was that a session appeared to keep working continuously, and the answer only visibly settled after pressing Escape. While debugging, we also observed multiple `opencode` servers rooted at the same project directory. That made the next problem different from "can ocman find an OpenCode port at all?": ocman could find a port, but the directory -> port mapping was ambiguous when more than one matching server existed. ## What we observed while debugging - Multiple OpenCode servers can exist for the same project directory, especially after repeated manual/ocman launches while testing session startup. - OpenCode port discovery is keyed by directory, so duplicate servers for one directory are ambiguous. - The UI has several live paths for a session: session-detail REST fetches, `/event` SSE proxying, composer sends, shell/command operations, model/info fetches, etc. - Before this patch, those paths resolved the directory -> port independently, so they could disagree if discovery returned a different server later. - Splitting a single session's live traffic across duplicate servers can produce confusing UI state, because the event stream and write path are no longer guaranteed to observe the same live process. ## What we inferred / assumed - We inferred the confusing live behavior was caused by session traffic moving between duplicate OpenCode servers for the same directory. - We assumed that once ocman has selected a live port for a session, consistency is more important than repeatedly rediscovering a potentially different same-directory server. - We assumed session affinity should be in-memory and best-effort: it improves live routing while ocman is running, without introducing new persistent state. - We assumed stale affinity should be cleared when the chosen port clearly stops working, so a later lookup can rediscover a replacement server. - We assumed users should be warned when duplicate servers exist, because affinity makes the current session more stable but does not remove the underlying ambiguity. ## Important caveats / implications - This does **not** prevent duplicate OpenCode servers from existing. It makes ocman more tolerant and makes the duplicate-server state visible. - The first selected port can still be arbitrary when several servers advertise the same directory. This PR is about consistency after selection, not about proving which duplicate server is "best". - Affinity is process-local and in-memory. Restarting ocman clears it, after which sessions are pinned again as live traffic is resolved. - Affinity is keyed by session ID, not process ID. If an OpenCode process dies and another server takes over, ocman recovers through failed fetch/connect/send paths and fresh discovery. - The warning is non-blocking. It tells the user duplicate servers may make live data ambiguous, but it does not choose or terminate servers for them. - Duplicate-server warning discovery is TTL-cached with the rest of port discovery. A warning can lag reality briefly, and launch/restart paths invalidate the cache when ocman knows the process set may have changed. - The send retry path avoids retrying typed OpenCode 4xx errors so real upstream rejections, such as validation/model errors, are not hidden by a second send attempt. ## Stack Base: `codex/frontend-test-lint-hygiene` (#5). ## Validation Run on `codex/opencode-port-affinity` after folding the warning branch into this PR and amending the warning discovery cache: - `go test ./internal/platforms/opencode ./internal/server` — passed. - `cd frontend && pnpm lint` — passed. - `make test` — passed (`105` frontend test files, `1277` frontend tests). - `make lint` — still fails at the existing base-branch host-helper check in `internal/server/loop_engine.go`. GitNexus: - Refreshed the stale index with `npx gitnexus analyze` before the cache edit. - Impact analysis for the mapped port-discovery symbol reported LOW risk. - `npx gitnexus detect-changes` mapped the final patch to port-discovery cache/invalidation symbols and the tmux launch/restart flows, which is the expected affected area for this change.
peter force-pushed codex/opencode-port-affinity from 5175e2288d to 3c5737d324 2026-06-23 19:21:01 +02:00 Compare
peter force-pushed codex/opencode-port-affinity from 3c5737d324 to 013123262c 2026-06-23 19:48:49 +02:00 Compare
peter force-pushed codex/opencode-port-affinity from 013123262c to d2774646a9 2026-06-24 09:01:37 +02:00 Compare
peter force-pushed codex/opencode-port-affinity from d2774646a9 to 86f8a55930 2026-06-24 12:00:55 +02:00 Compare
peter changed target branch from codex/opencode-fresh-port-discovery to main 2026-06-24 12:02:46 +02:00
peter force-pushed codex/opencode-port-affinity from 7a65d789bf to 4ab97fb916 2026-06-24 12:23:04 +02:00 Compare
peter changed target branch from main to codex/frontend-test-lint-hygiene 2026-06-24 12:23:47 +02:00
peter force-pushed codex/opencode-port-affinity from 4ab97fb916 to f6c08a5915 2026-06-24 12:44:03 +02:00 Compare
peter changed title from fix(opencode): keep session live traffic on one port to fix(opencode): handle duplicate OpenCode servers 2026-06-24 14:07:30 +02:00
peter force-pushed codex/opencode-port-affinity from daed338449 to 8eb91a8e36 2026-06-24 14:22:34 +02:00 Compare
peter changed target branch from codex/frontend-test-lint-hygiene to main 2026-06-24 23:15:50 +02:00
peter closed this pull request 2026-06-25 08:59:24 +02:00

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
peter/ocman!4
No description provided.