Skip to content

feat(opencode): add SSE replay for missed session events#401

Merged
Astro-Han merged 13 commits intodevfrom
codex/i396-sse-replay
May 3, 2026
Merged

feat(opencode): add SSE replay for missed session events#401
Astro-Han merged 13 commits intodevfrom
codex/i396-sse-replay

Conversation

@Astro-Han
Copy link
Copy Markdown
Owner

@Astro-Han Astro-Han commented May 3, 2026

Summary

  • Add bounded replay IDs and storage for replayable global SSE events.
  • Wire global /event reconnect replay with Last-Event-ID cursors while keeping non-replayable live events on the existing bus path.
  • Avoid server.connected refresh on valid reconnects with full replay, while still advancing invalid/gapped cursors.
  • Persist the app replay cursor across outer SDK reconnects and guard stale question/permission asks after terminal replies.
  • Add deterministic UI e2e coverage for missed question.asked replay and stale question.asked after reply.

Why

Issue #396 needs short-window recovery for missed question, permission, and session/status updates after SSE reconnects. This keeps recovery at the transport layer around the existing global BusEvent stream, without moving blocker/session updates into persistent sync-event storage.

Related Issue

Refs #396. This PR implements the backend replay path, app cursor persistence, reducer stale-blocker guard, route-level SSE replay coverage, and focused UI e2e coverage for question replay/stale-ask behavior. Full UI e2e coverage for replay-unavailable fallback remains a follow-up before #396 should be considered completely closed.

Human Review Status

Pending. A human should make the final merge decision after reviewing the final diff and verification evidence.

Review Focus

  • Replay/live ordering in packages/opencode/src/server/event-replay.ts and packages/opencode/src/server/instance/global.ts.
  • Cursor advancement rules: fresh connections seed a cursor, valid reconnects replay without server.connected, and invalid/gap reconnects send one server.connected with the fence id.
  • Replay gap detection when retained records are partially or fully pruned.
  • Dispose invalidation for retained replay records.
  • App-side cursor persistence in packages/app/src/context/global-sdk.tsx and terminal blocker cache behavior in global-sync.
  • E2E-only question hooks in packages/opencode/src/server/instance/question.ts, which are gated by OPENCODE_E2E_LLM_URL and used only for deterministic UI transport tests.

Risk Notes

  • The global /event stream now assigns ids for replayable events, so reconnect behavior changes for clients using Last-Event-ID.
  • Replay is in-memory only and bounded by count and age; stale cursors, pruned replay windows, or process restarts intentionally fall back to normal bootstrap/fallback behavior.
  • Valid reconnect no longer emits server.connected, so replay ordering and the terminal blocker guard now carry the short-gap recovery path instead of a refresh masking it.
  • Invalid/gapped reconnects skip partial replay and send one server.connected(fenceID); bootstrap owns recovery when the replay window cannot prove continuity.
  • global.disposed resets the replay generation and clears retained records instead of being stored as a replay record; online clients receive the live packet, while offline clients refresh after bootID mismatch.
  • Existing LLM-driven question seed tests are still flaky in this environment. The new replay UI e2e uses an e2e-only backend hook to publish real Question.ask() / question.asked events without depending on model seeding.
  • Maintainer labeling request: please add the appropriate type, scope, and priority labels.

How To Verify

Server replay/route tests: 26 passed via bun test test/server/event-replay.test.ts test/server/global-event-replay.test.ts from packages/opencode
Question replay UI e2e: 2 passed via bun run --cwd packages/app test:e2e -- session/session-composer-dock.spec.ts -g "SSE replay|stale question\\.asked"
App focused tests: 35 passed via bun test --preload ./happydom.ts src/context/global-sdk/sse-cursor.test.ts src/context/global-sync/blocker-terminal-cache.test.ts src/context/global-sync/event-reducer.test.ts src/pages/session/blockers/question-fallback.test.ts src/pages/session/blockers/question-reconcile.test.ts src/pages/session/blockers/question-refetch-runner.test.ts
Opencode typecheck: passed via bun run --cwd packages/opencode typecheck
App typecheck: passed via bun run --cwd packages/app typecheck
Diff check: passed via git diff --check
GitHub checks: pending after latest push

Screenshots or Recordings

Not required. This PR changes transport/reducer behavior and tests, with no visible UI copy or layout change.

Checklist

  • Human review status is stated above as pending, approved, or not required
  • I linked the related issue, or stated why there is no issue
  • This PR has type, scope, and priority labels, or I requested maintainer labeling
  • I described the review focus and any meaningful risks
  • I listed the relevant verification steps and the key result for each
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope
  • I manually checked visible UI or copy changes when needed, with screenshots or recordings
  • I considered macOS and Windows impact for desktop, packaging, updater, signing, paths, shell, or permissions changes
  • I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant
  • I reviewed the final diff for unrelated changes and suspicious dependency changes
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English

Summary by CodeRabbit

  • New Features

    • SSE event replay with durable cursor support and a controllable client-side stream for robust reconnection recovery.
    • E2E hooks/endpoints and fixture support to seed/publish question events and expose backend URL.
  • Bug Fixes

    • Richer session timeout errors with contextual details for easier diagnosis.
    • Prevented stale or duplicate question/permission prompts via a terminal-blocking cache.
  • Tests

    • Added unit, integration, and E2E tests covering replay, cursor, blocker cache, and question-dock recovery.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ac7fc275-83b1-4232-97b2-eadbc10fbe7c

📥 Commits

Reviewing files that changed from the base of the PR and between 7edb06b and 77ca52e.

📒 Files selected for processing (2)
  • packages/app/e2e/backend.ts
  • packages/opencode/src/server/instance/question.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/opencode/src/server/instance/question.ts

📝 Walkthrough

Walkthrough

Adds server-side SSE replay (EventReplayStore, bridge, replay-aware /event stream), a client SSE replay cursor and E2E controls, a blocker-terminal cache to prevent reopening resolved asks/permissions, E2E endpoints/fixtures and tests, SDK wiring to expose/start/stop the stream for E2E, and an improved e2e timeout error that concurrently fetches questions and session status.

Changes

SSE Event Replay, Client Cursor, and Blocker Terminal Cache

Layer / File(s) Summary
Data Shape & Helpers
packages/opencode/src/server/event-replay.ts, packages/app/src/context/global-sdk/sse-cursor.ts, packages/app/src/context/global-sync/blocker-terminal-cache.ts
Adds replay-related types (GlobalEventEnvelope, ReplayCursor, ReplayRecord, ReplaySnapshot), parseReplayCursor() and isReplayableGlobalEvent(), createSseCursor(), and createBlockerTerminalCache() factory and types.
Core Store Implementation
packages/opencode/src/server/event-replay.ts
Implements EventReplayStore with boot-scoped monotonic seq, append, snapshot (fence/gap/invalidCursor), TTL/max-record pruning, clearDirectory, reset, and deep-cloning of stored envelopes.
Server Bridge & Streaming
packages/opencode/src/server/instance/global.ts
Adds createGlobalEventReplayBridge(), openGlobalEventReplayConnection(), and streamGlobalEvents(); wires bridge to GlobalBus, resets/clears replay state on disposal events, seeds from Last-Event-ID, emits server.connected when required, streams replay records then buffered live packets, and replaces prior live-only /event behavior.
Server E2E Question Endpoints
packages/opencode/src/server/instance/question.ts
Adds E2E-only POST /__e2e/ask and /__e2e/publish-asked routes (gated by env) to trigger Question.Service.ask and publish Question.Event.Asked.
Client SDK Cursor Wiring
packages/app/src/context/global-sdk.tsx, packages/app/src/context/global-sdk/sse-cursor.ts
Global SDK creates a replayCursor, attaches replayCursor.headers() to SSE requests, advances cursor on each SSE event, and exposes E2E window controls to start/stop the stream and read the cursor.
Blocker Terminal Cache & Sync Wiring
packages/app/src/context/global-sync/blocker-terminal-cache.ts, packages/app/src/context/global-sync.tsx
Adds blocker-terminal cache implementation; instantiates cache in global sync, clears directory entries on child disposal, and passes cache into applyDirectoryEvent.
Event Reducer Deduplication
packages/app/src/context/global-sync/event-reducer.ts
applyDirectoryEvent() gains optional blockerTerminals param; *.asked events short-circuit when has() returns true; *.replied/*.rejected mark terminal state before updating store to prevent later stale asks from reopening.
E2E & Test Fixtures
packages/app/e2e/fixtures.ts, packages/app/src/testing/terminal.ts
E2E fixtures expose backend URL; E2E window typing adds optional globalEventStream controls.
E2E Helpers & Tests
packages/app/e2e/session/session-composer-dock.spec.ts, packages/app/e2e/actions.ts
Adds browser-side global event stream helpers, HTTP helpers for asking/publishing questions, poll helpers, two session-composer dock E2E tests, and an improved seedSessionQuestion timeout that concurrently fetches questions and session status and includes them in the thrown error.
Unit / Integration Tests
packages/opencode/test/server/event-replay.test.ts, packages/opencode/test/server/global-event-replay.test.ts, packages/app/src/context/global-sdk/sse-cursor.test.ts, packages/app/src/context/global-sync/blocker-terminal-cache.test.ts, packages/app/src/context/global-sync/event-reducer.test.ts
Adds coverage for cursor parsing, replayable filtering, store append/prune/snapshot/gap/invalidCursor, bridge/connection replay and buffering, SSE header usage and cursor updates, blocker cache TTL/pruning/clearDirectory, and out-of-order ask/reply deduplication.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant SDK as Global SDK
    participant Server as /event SSE
    participant Replay as EventReplayStore
    participant Bus as GlobalBus

    Client->>SDK: connect (GET /event with Last-Event-ID?)
    SDK->>SDK: replayCursor.headers() -> attach header
    SDK->>Server: GET /event (with Last-Event-ID)
    Server->>Replay: snapshot(lastEventID)
    Replay-->>Server: replay records + fence/gap info
    Server-->>Client: replayed SSE packets (with ids)
    Bus->>Server: emit live envelope events
    Server->>Replay: append(envelope)
    Server-->>Client: buffered live SSE packets (post-replay)
    Client->>SDK: onSseEvent(event)
    SDK->>SDK: replayCursor.update(event.id)
Loading
sequenceDiagram
    participant EventStream
    participant Reducer
    participant Cache
    participant Store

    EventStream->>Reducer: question.replied(session, req)
    Reducer->>Cache: mark("question", dir, session, req)
    Reducer->>Store: remove pending question

    Note over EventStream,Reducer: Later (stale) ask arrives
    EventStream->>Reducer: question.asked(session, req)
    Reducer->>Cache: has("question", dir, session, req)?
    Cache-->>Reducer: true
    Reducer->>Reducer: short-circuit (skip creating pending question)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested labels

enhancement, P1, app, harness

Poem

🐰 I nibbled cursors, kept last-IDs bright,

Replayed lost whispers back into the night.
I fenced the stream and buffered the flow,
Blocked stale asks so the state could grow.
Hops and heartbeats — the timeline's right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically describes the main change: adding SSE replay functionality for missed session events. It directly corresponds to the core feature implemented across the changeset.
Description check ✅ Passed The PR description comprehensively covers all required template sections including Summary, Why, Related Issue, Review Focus, Risk Notes, How To Verify, and a completed Checklist with meaningful detail.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/i396-sse-replay

Review rate limit: 9/10 reviews remaining, refill in 6 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces an SSE event replay system to enhance client-server synchronization, featuring a server-side EventReplayStore and a client-side sse-cursor. It also adds a blocker-terminal-cache to prevent stale events from reopening resolved requests. Feedback highlights that the server.connected event is sent unconditionally, which may trigger redundant client-side refreshes and bypass the replay optimization.

Comment thread packages/opencode/src/server/instance/global.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/app/src/context/global-sync/event-reducer.test.ts (1)

497-555: ⚡ Quick win

Add a question.rejected-before-question.asked variant test.

question.rejected shares the same terminal path as question.replied; adding the mirrored stale-order case would close the last branch-level gap.

Proposed test addition
+  test("question.rejected before question.asked prevents stale ask from reopening", () => {
+    const blockerTerminals = createBlockerTerminalCache({ now: () => 1000 })
+    const [store, setStore] = createStore(baseState())
+
+    applyDirectoryEvent({
+      event: { type: "question.rejected", properties: { sessionID: "ses_1", requestID: "q1" } },
+      directory: "/repo",
+      store,
+      setStore,
+      push() {},
+      loadLsp() {},
+      blockerTerminals,
+    })
+
+    applyDirectoryEvent({
+      event: {
+        type: "question.asked",
+        properties: questionRequest("q1", "ses_1"),
+      },
+      directory: "/repo",
+      store,
+      setStore,
+      push() {},
+      loadLsp() {},
+      blockerTerminals,
+    })
+
+    expect(store.question.ses_1).toBeUndefined()
+  })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/src/context/global-sync/event-reducer.test.ts` around lines 497
- 555, Add a new unit test mirroring "question.replied before question.asked
prevents stale ask from reopening" but use the "question.rejected" event type:
create blockerTerminals with now:() => 1000, create a fresh store via
createStore(baseState()), call applyDirectoryEvent first with event.type
"question.rejected" and properties { sessionID: "ses_1", requestID: "q1" }, then
call applyDirectoryEvent with event.type "question.asked" and properties from
questionRequest("q1","ses_1"), and finally assert
expect(store.question.ses_1).toBeUndefined(); use the same helpers
(createBlockerTerminalCache, createStore, applyDirectoryEvent, questionRequest)
and same args for directory, setStore, push, loadLsp as the existing tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/server/event-replay.ts`:
- Around line 105-129: open() computes replay slices from this.records without
pruning by age, so stale records survive quiet periods; before computing
fenceSeq/earliestSeq/replay, invoke the same expiration logic used in append
(respecting maxAgeMs) to remove aged-out entries from this.records (or call the
existing prune/expire helper if present) so replay only includes non-expired
records; ensure this affects earliestSeq and replay calculation and preserve
listener behavior.

---

Nitpick comments:
In `@packages/app/src/context/global-sync/event-reducer.test.ts`:
- Around line 497-555: Add a new unit test mirroring "question.replied before
question.asked prevents stale ask from reopening" but use the
"question.rejected" event type: create blockerTerminals with now:() => 1000,
create a fresh store via createStore(baseState()), call applyDirectoryEvent
first with event.type "question.rejected" and properties { sessionID: "ses_1",
requestID: "q1" }, then call applyDirectoryEvent with event.type
"question.asked" and properties from questionRequest("q1","ses_1"), and finally
assert expect(store.question.ses_1).toBeUndefined(); use the same helpers
(createBlockerTerminalCache, createStore, applyDirectoryEvent, questionRequest)
and same args for directory, setStore, push, loadLsp as the existing tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1cfa4684-d3c6-4285-8c94-977dbc736a3e

📥 Commits

Reviewing files that changed from the base of the PR and between 7635333 and edb5b09.

📒 Files selected for processing (13)
  • packages/app/e2e/actions.ts
  • packages/app/src/context/global-sdk.tsx
  • packages/app/src/context/global-sdk/sse-cursor.test.ts
  • packages/app/src/context/global-sdk/sse-cursor.ts
  • packages/app/src/context/global-sync.tsx
  • packages/app/src/context/global-sync/blocker-terminal-cache.test.ts
  • packages/app/src/context/global-sync/blocker-terminal-cache.ts
  • packages/app/src/context/global-sync/event-reducer.test.ts
  • packages/app/src/context/global-sync/event-reducer.ts
  • packages/opencode/src/server/event-replay.ts
  • packages/opencode/src/server/instance/global.ts
  • packages/opencode/test/server/event-replay.test.ts
  • packages/opencode/test/server/global-event-replay.test.ts

Comment thread packages/opencode/src/server/event-replay.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/server/event-replay.ts`:
- Around line 75-79: Validate retention configuration in the EventReplay
constructor (and the other initialization path around lines 149-153) to prevent
negative retention values: check that maxRecords and maxAgeMs are not negative
(e.g., if input?.maxRecords < 0 or input?.maxAgeMs < 0) and either throw a clear
ArgumentError or clamp to a safe non-negative value; this ensures prune()'s loop
condition cannot be satisfied indefinitely and prevents append()/snapshot() from
hanging. Include the checks where bootID, maxRecords, maxAgeMs, and now are
assigned so invalid inputs are rejected early.

In `@packages/opencode/test/server/global-event-replay.test.ts`:
- Around line 14-25: The helper readSseUntil currently can hang indefinitely if
the predicate never matches; modify readSseUntil to fail fast by adding a
timeout (e.g., timeoutMs param with a sensible default) and use an
AbortController or a timeout Promise to cancel the response.body.getReader()
(call reader.cancel()) and reject with a clear timeout error if the predicate
isn’t satisfied within that window; keep the existing loop and TextDecoder logic
but wrap the read loop in a Promise.race (or respect AbortSignal) so the
function throws on timeout instead of waiting for the test framework to time
out.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 96d4a219-8ef3-467d-bc3f-9f559820b261

📥 Commits

Reviewing files that changed from the base of the PR and between edb5b09 and 8cb62d8.

📒 Files selected for processing (6)
  • packages/app/src/context/global-sdk/sse-cursor.ts
  • packages/app/src/context/global-sync/blocker-terminal-cache.ts
  • packages/opencode/src/server/event-replay.ts
  • packages/opencode/src/server/instance/global.ts
  • packages/opencode/test/server/event-replay.test.ts
  • packages/opencode/test/server/global-event-replay.test.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/app/src/context/global-sdk/sse-cursor.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/opencode/src/server/instance/global.ts

Comment thread packages/opencode/src/server/event-replay.ts
Comment thread packages/opencode/test/server/global-event-replay.test.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/opencode/src/server/event-replay.ts (1)

164-164: ⚡ Quick win

Add module namespace self-reexport at file end.

Please add the canonical module-as-namespace export for this new packages/opencode/src module so consumers can import it consistently.

Suggested diff
 export class EventReplayStore {
   // ...
 }
+
+export * as EventReplay from "./event-replay"

Based on learnings: In packages/opencode/src module files, the canonical pattern is a bottom self-reexport (export * as <ModuleName> from "./<module-file>") and consumers depend on that namespace import style.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/src/server/event-replay.ts` at line 164, Add a canonical
module namespace self-reexport at the end of the file so consumers can import
the module as a namespace; specifically append an export statement that
re-exports everything from this file's module (e.g., export * as EventReplay
from "./event-replay") at the bottom of packages/opencode/src/event-replay.ts so
consumers can import the EventReplay namespace consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/test/server/global-event-replay.test.ts`:
- Around line 168-173: Add an explicit assertion that the SSE route returned a
successful HTTP status before consuming the stream: after calling
app.request(...) and before calling readSseUntil(...), check the response status
(e.g., ensure response.status is 200 or within the expected range) so failures
in route handling are reported immediately; update the test around the response,
controller, and readSseUntil usage to include this status assertion.

---

Nitpick comments:
In `@packages/opencode/src/server/event-replay.ts`:
- Line 164: Add a canonical module namespace self-reexport at the end of the
file so consumers can import the module as a namespace; specifically append an
export statement that re-exports everything from this file's module (e.g.,
export * as EventReplay from "./event-replay") at the bottom of
packages/opencode/src/event-replay.ts so consumers can import the EventReplay
namespace consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4cf77321-1a9a-4135-9a00-97950b6b91a9

📥 Commits

Reviewing files that changed from the base of the PR and between 844cf4f and 219c114.

📒 Files selected for processing (3)
  • packages/opencode/src/server/event-replay.ts
  • packages/opencode/test/server/event-replay.test.ts
  • packages/opencode/test/server/global-event-replay.test.ts

Comment thread packages/opencode/test/server/global-event-replay.test.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/app/e2e/session/session-composer-dock.spec.ts (1)

470-481: 🏗️ Heavy lift

Make this assertion distinguish replay from fallback recovery.

After stream.start(), this only checks that the dock becomes blocked again. That same end state is also valid for the normal bootstrap/refresh fallback described in the PR, so this test can stay green even if reconnect replay is broken. Please add a replay-specific assertion here instead of only asserting eventual dock recovery.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/e2e/session/session-composer-dock.spec.ts` around lines 470 -
481, The test currently only asserts dock recovery after stream.start() which
can't distinguish replay from a generic fallback; modify the test to assert a
replay-specific signal emitted by the reconnection flow before/while the dock
unblocks. After calling stream.start() (and before or immediately after
expectQuestionBlocked()), poll or inspect globalEventStream(stream.cursor) for a
replay-specific event or marker (for example a "replay"/'replayed_events' log
entry or a replay-related payload property) and assert it appears within the
timeout, and/or assert that the restored question(s) include replay metadata
(e.g. a replay flag on the restored question object) rather than relying solely
on expectQuestionBlocked() or questionDockSelector count to prove recovery.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/server/instance/question.ts`:
- Around line 30-37: The current detached call to
AppRuntime.runPromise(Question.Service.use(... svc.ask(...))) swallows errors
via .catch(() => undefined); change this so startup/persistence failures are
surfaced or at least logged before the request is enqueued: in the block that
calls AppRuntime.runPromise and Question.Service.use with svc.ask, remove the
empty catch and instead handle rejections by logging the error (using your
system logger/processLogger) or rethrowing for upstream handling; ensure
failures that occur inside svc.ask are not converted into a silent 204 and that
waitForQuestionSeed can observe the error.

---

Nitpick comments:
In `@packages/app/e2e/session/session-composer-dock.spec.ts`:
- Around line 470-481: The test currently only asserts dock recovery after
stream.start() which can't distinguish replay from a generic fallback; modify
the test to assert a replay-specific signal emitted by the reconnection flow
before/while the dock unblocks. After calling stream.start() (and before or
immediately after expectQuestionBlocked()), poll or inspect
globalEventStream(stream.cursor) for a replay-specific event or marker (for
example a "replay"/'replayed_events' log entry or a replay-related payload
property) and assert it appears within the timeout, and/or assert that the
restored question(s) include replay metadata (e.g. a replay flag on the restored
question object) rather than relying solely on expectQuestionBlocked() or
questionDockSelector count to prove recovery.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 39678a08-a771-4db2-8723-8653a14e0e2f

📥 Commits

Reviewing files that changed from the base of the PR and between dbfc31b and 74a5a49.

📒 Files selected for processing (5)
  • packages/app/e2e/fixtures.ts
  • packages/app/e2e/session/session-composer-dock.spec.ts
  • packages/app/src/context/global-sdk.tsx
  • packages/app/src/testing/terminal.ts
  • packages/opencode/src/server/instance/question.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/app/src/context/global-sdk.tsx

Comment thread packages/opencode/src/server/instance/question.ts Outdated
@Astro-Han Astro-Han force-pushed the codex/i396-sse-replay branch from 4adebd1 to 7edb06b Compare May 3, 2026 05:37
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/server/instance/question.ts`:
- Around line 30-31: Replace the ad-hoc check of OPENCODE_E2E_LLM_URL with a
strict dedicated E2E enable guard: add or use a single environment flag like
OPENCODE_ENABLE_E2E_ROUTES (or helper function isE2EEnabled()) and change the
early-return gate in the question route (the line with if
(!Env.get("OPENCODE_E2E_LLM_URL")) return c.notFound()) to check that flag
instead; update the other E2E route guards referenced (the checks at the other
route locations mentioned) to reuse the same flag/helper so both routes are
protected by the explicit test-mode guard.
- Around line 24-27: The route schema currently allows any number of questions;
restrict the questions array to the same 1..4 bounds as Question.Request to
avoid invalid seeds. Update the zod schema (the object containing SessionID.zod
and questions: z.array(Question.Info.zod)) to enforce .min(1).max(4) on the
questions array (or replace it with the validated
Question.Request/Question.Request.zod schema) so the route matches
Question.Request constraints.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0bf3191c-a7eb-4b5e-b414-5b787d8b8a50

📥 Commits

Reviewing files that changed from the base of the PR and between 79b97dd and 4adebd1.

📒 Files selected for processing (2)
  • packages/app/src/context/global-sdk.tsx
  • packages/opencode/src/server/instance/question.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/app/src/context/global-sdk.tsx

Comment thread packages/opencode/src/server/instance/question.ts
Comment thread packages/opencode/src/server/instance/question.ts Outdated
@Astro-Han Astro-Han merged commit cd8d47f into dev May 3, 2026
37 of 38 checks passed
@Astro-Han Astro-Han deleted the codex/i396-sse-replay branch May 3, 2026 07:52
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.

1 participant