🤖 fix: keep optimistic goal visible during streaming#3602
Conversation
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c34fdf6c53
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
A goal set while the agent is streaming is published only as optimistic, transient state in WorkspaceGoalService; the durable goal.json write is deferred until stream-end. Activity snapshots emitted mid-stream from persisted metadata (status_set, todo_write, recency, streaming) still carry the pre-stream goal, and each one fully replaced the frontend activity entry, dropping the optimistic goal so it flickered out of the Goal tab until the next goal read re-overlaid it. Overlay the goal service's pending snapshot in WorkspaceService.emitWorkspaceActivity so every activity emit surfaces the optimistic goal while it is queued. The goal service clears its pending snapshot before emitting authoritative reverts (user abort) or durable persistence (stream-end), so those transitions win naturally and the discarded goal does not linger after an abort.
c34fdf6 to
7bb107f
Compare
|
Addressed the P2 abort-revert concern (thread PRRT_kwDOPxxmWM6LIK0V). The earlier renderer-side guard was the wrong layer precisely because of this race: the renderer cannot tell a stale mid-stream activity emit apart from the authoritative abort-revert — both carry the persisted goal and can arrive while The fix now lives in |
|
@codex review Reworked the fix to live in the backend (WorkspaceService.emitWorkspaceActivity overlays the goal service's pending snapshot) instead of the renderer, which resolves the abort-revert race you flagged. Please take another look. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7bb107ff05
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
getActivityList() builds straight from persisted extensionMetadata snapshots, so a renderer reconnect/reload (or ACP client) during a mid-stream goal set seeded the UI with the stale pre-stream goal until the next live emit. Apply the same pending-goal overlay there.
|
Addressed (thread PRRT_kwDOPxxmWM6LIOj3): |
|
@codex review Applied the pending-goal overlay to |
|
Codex Review: Didn't find any major issues. Bravo. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
Fixes a transient UI bug where a goal set while the agent is streaming would briefly disappear from the Goal tab (it appears, vanishes, then comes back).
Background
When a goal is set mid-stream,
WorkspaceGoalServiceintentionally defers the durablegoal.jsonwrite until stream-end and holds the new goal as optimistic, transient state (pendingGoalSnapshots, surfaced to the renderer as apendingPersistencegoal).goal.json(and the persisted activity metadata) keep the pre-stream goal.The problem: the live stream keeps emitting full workspace activity snapshots —
status_set,todo_write/propose_plan, recency bumps, streaming-status updates — each built from persisted metadata, so each still carries the stale pre-stream goal. In the renderer those non-transient snapshots fully replace the in-memory activity entry, dropping the optimistic goal. It only reappeared on the next goal read or at stream-end, producing the flicker:Implementation
The fix lives at the single activity-emit choke point,
WorkspaceService.emitWorkspaceActivity. Before emitting, it overlays the goal service's pending optimistic snapshot (via the newWorkspaceGoalService.getPendingGoalSnapshot) onto the activity snapshot'sgoalfield, so every mid-stream emit surfaces the optimistic goal instead of replaying the stale one.This is robust because the goal service owns the pending state and clears it before emitting authoritative transitions:
transientGoalOnly) and are skipped by the overlay.recordUserStoppedStream) deletes the pending snapshot before pushing the reverted goal, so the overlay no longer fires and the discarded goal correctly disappears.applyPendingAfterStreamEnd) likewise clears the pending snapshot before pushing the durable goal.A frontend-only guard was considered but rejected: the renderer cannot distinguish a stale mid-stream emit from an authoritative abort-revert (both carry the persisted goal and can arrive while
streamingis stilltrue), so any renderer heuristic would wrongly pin a discarded goal after an Esc/abort. Anchoring the fix in the goal-state owner avoids that ambiguity.Validation
WorkspaceServicetest drives the real path: queue a goal mid-stream, fire astatus_set-style activity emit, and assert the emitted snapshot surfaces the optimistic (pendingPersistence) goal — thenrecordUserStoppedStreamand assert a subsequent emit reverts to the persisted goal with no lingering optimistic state.make static-checkand theworkspaceService/workspaceGoalService/WorkspaceStoresuites pass.Risks
Low. The overlay is a narrow read-only lookup at emit time that only changes behavior while a goal mutation is queued (mid-stream). Authoritative goal emits are untouched, and the active-goal count already excludes pending goals. The main area to watch is goal-tab/sidebar display during streaming, which the new test exercises end-to-end including the abort path.
Generated with
mux• Model:anthropic:claude-opus-4-8• Thinking:xhigh• Cost:n/a