Skip to content

fix(desktop): restore transcript Retry after initial load failures - #5974

Open
chihumyum wants to merge 3 commits into
apache:mainfrom
chihumyum:fix/desktop-transcript-retry
Open

chihumyum wants to merge 3 commits into
apache:mainfrom
chihumyum:fix/desktop-transcript-retry

Conversation

@chihumyum

Copy link
Copy Markdown
Contributor

Summary

When the first transcript read and its automatic recovery both fail, Retry does nothing because it can only reach a controller after a successful publication. Keep the current observation controller available to Retry so the next read restores the conversation. Check session selection and observation identity to reject stale commands and errors.

This covers transcript Retry only. Published-history readiness and the observation effect's cleanup ownership remain intact.

Verification

  • The reader regression failed before the fix with Retry must issue another transcript read before any publication: 2 !== 3. The 10 new regression cases now pass, and 153 related conversation, transcript, and navigation tests pass through node --test.
  • npm run build, npm run typecheck, npm run lint, npm run format:check, and Knip for desktop and UI pass. Renderer architecture against the base, Astryx surface inventory, Windows inventory, app-shell hooks, E2E budget, and ASF headers pass.
  • Actual Electron window with this build and isolated fixture data: injected initial and automatic read failures, clicked the visible Retry button, and verified the transcript returned and Retry disappeared. Used the existing preload E2E latch, without a real account. The full cross-platform suite was not run locally.

AI use

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Codex with GPT-6 Astra for investigation, implementation, regression tests, review, and verification. Both commits include Generated-by: Codex.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

Exercise the reader Retry callback through the observation lifecycle and real transcript controller. Cover initial recovery failure, repeated Retry, session handoff, observation replacement, and unmount.

Generated-by: Codex
Keep the observation controller available before the first transcript publication so Retry can recover after the initial read and automatic recovery fail. Fence retry admission and late errors by session selection and observation identity.

Generated-by: Codex
@github-actions github-actions Bot added the effort/M Under 500 readable lines label Oct 6, 2026

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.

Full review of 5948cb61 (two commits: d72579a0 adds the test and 5948cb61 the fix; +270/-7, 5 files). This PR hasn't been reviewed before, and it doesn't link an issue.

What changed. The observation effect now records { sessionId, controller } in a new workspace.observationRef, and clears it in cleanup only if it still points at the same observation. retryMessages reloads that controller instead of transcriptRangeRef.current, which is set only after a successful publication. It is guarded by observation.sessionId === sessionId and isSessionSelected(sessionId), and the error report is rechecked after the await.

Correctness.

  • The guard passes in exactly the failure case. isSessionSelected requires the published id activeIdRef to equal the session (session-workspace-actions.ts:231). On a read failure, applyReadError calls commitTranscript(sessionId, []) (use-conversation-observation.ts:71), which sets activeIdRef and leaves transcriptRangeRef undefined. So after a failure Retry can proceed, while Retry for a different selected Session is still rejected.
  • No controller divergence. Only the observation effect publishes into transcriptRangeRef (via publishTranscript/commitTranscript). There is one controller per effect run: re-subscribing to events reuses it. So observationRef.controller is always the controller that is displayed, or would be once published.
  • Lifecycle. The identity-checked clear in cleanup handles StrictMode re-mounts and authority-revision replacement. A reload in flight when the effect is torn down is closed by controller.close(), and its error is suppressed by the post-await identity and selection check. messageRetryPending is released in finally.
  • refreshMessages still uses transcriptRangeRef on purpose, since it needs a published range. That matches the stated scope.

Findings

  • P3: a failed Retry shows two error toasts (see the inline comment on transcript-commands.ts:58).
  • Nit, no grade: use-conversation-observation.ts:51 uses an inline import('../model/conversation-workspace.js') type. A top-level import type would match the rest of the file.

Tests. There are 10 cases that drive the real createDesktopTranscriptRangeController through ConversationLifecycle. They cover:

  • Retry after the initial read and the automatic recovery both fail (proved RED before the fix)
  • automatic recovery alone, and a failed Retry followed by a successful one
  • an A to B handoff, observation-authority replacement, and unmount with a pending read

No test asserts the toast count. Putting the test in apps/desktop/src/main/__tests__ follows the existing convention (47 conversation tests live there).

i18n: no UI copy changes, so all three locales are unaffected. Protocol: not touched.

Status: MERGEABLE (merge state BLOCKED, review required). The head is on current main 3597abe8, git merge-tree is clean, and git diff --check is clean. Hosted CI (label and test) passes on 5948cb61. I did not run the suites or the Electron UI locally. This review is not merge approval.

Comment thread apps/desktop/src/renderer/features/conversation/model/transcript-commands.ts Outdated
The range controller's reload() hands a failure to its recovery, which
reports it through the observation's onError (the "Failed to load task"
toast and read error), and then rethrows. retryMessages reported the
rethrown error again as a refresh failure, so one failed Retry raised
two toasts and replaced the read error with the refresh message.

Leave the report to the controller, as its own gap and recovery reloads
do, and only release the pending Retry. The controller still withholds
a reload that lands on the cached transcript, so Retry stays quiet then
too. refreshMessages never calls reload() and keeps its own report.

The retry test now records error toasts and checks that a failed Retry
raises exactly one, keeps the read error, and that a successful Retry
raises none.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/M Under 500 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants