fix(coordinator): clear focus_target on cancel regardless of phase#387
Merged
H-Chris233 merged 1 commit intobetafrom May 9, 2026
Merged
fix(coordinator): clear focus_target on cancel regardless of phase#387H-Chris233 merged 1 commit intobetafrom
H-Chris233 merged 1 commit intobetafrom
Conversation
finish_cancel_session_state previously only cleared state.focus_target when the cancelled phase was not Processing — Processing-phase cancel left the stale window-resource handle in place until the next begin_session_state overwrote it. Between cancel and next begin, any reader of focus_target sees a value belonging to a session that is about to be torn down. Move state.focus_target = None ahead of the phase check so it always fires when the cancel was accepted (i.e. not rejected for Idle / Inserting phases). Phase transition stays conditional on the original non-Processing branch, since end_session must still own the Processing → Idle collapse to avoid racing with polish/insert. Tighten cancel_session_state_machine_is_table_driven so it asserts focus_target gets cleared for every accepted phase (including Processing) and stays untouched on rejected cancels. Audit ID 3.3.5.
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
This was referenced May 10, 2026
pull Bot
pushed a commit
to yimmy23/openless
that referenced
this pull request
May 10, 2026
Two real bugs surfaced by the 2026-05-10 end-to-end logic review of beta after the 8-PR audit-fix wave landed. 1. coordinator.rs:2313 — `acquire_recording_mute(inner, "qa");` was missing `.await`. PR Open-Less#391 (`6171df61`) made the function `async fn` and updated the dictation call site at coordinator/dictation.rs:451, but the QA call site was missed. Effect: when a user has `mute_during_recording = true` and triggers QA via Right Option, the returned Future is dropped on the next line, `spawn_blocking` is never scheduled, holders never increments, and system audio is NOT muted (e.g. YouTube playback continues into the QA recording). Both the matching `release_recording_mute(inner, "qa")` calls become no-ops (early return at holders == 0). The compiler was emitting `unused_must_use` for this site (verified before this commit). Fix: add `.await`. 2. coordinator/dictation.rs:843-849 — PR Open-Less#387 (`ce82fcd9`) was framed as "clear focus_target on cancel regardless of phase", but the only code path that gained the unconditional clear was `finish_cancel_session_state`. cancel_session deliberately skips that helper for Processing (so end_session can drive its own teardown), and end_session's "ASR-finished, cancelled" exit at dictation.rs:843-849 set phase=Idle but never touched focus_target. Result: cancel-during-Processing leaves a stale `usize` focus slot in state.focus_target until the next begin_session_state overwrites it. Today the leak is bounded (no documented reader between cancel and next begin), but it violates PR Open-Less#387's stated contract and is a silent footgun for any future reader of focus_target on that interval. Fix: in the cancelled-after-ASR exit branch, take the state lock once and clear both phase and focus_target together. Source: docs/logic-review-2026-05-10.md (subagent end-to-end review). 185/185 lib tests pass; cargo check clean (unused_must_use gone). Manual verification checklist for both fixes in the review doc.
pull Bot
pushed a commit
to yimmy23/openless
that referenced
this pull request
May 10, 2026
10 PRs landed on beta this cycle: - Open-Less#377 paste shortcut configurable (issue Open-Less#360) - Open-Less#386 TS UserPreferences updateChannel alignment - Open-Less#387 focus_target leak on Processing-phase cancel - Open-Less#388 [严重] MacHotkeyAdapter::shutdown stops CFRunLoop + tap - Open-Less#389 emit_capsule window.show/hide off audio thread - Open-Less#390 QA / dictation hotkey routing race - Open-Less#391 audio-mute spawn_blocking (async hygiene) - Open-Less#392 hotkey supervisor + global dispatcher exit signal - Open-Less#393 post-audit logic-review hotfixes (QA mute .await + focus_target Processing branch) - Open-Less#394 in-process credentials cache (kills repeated Keychain prompts) Bump 4 files: package.json, tauri.conf.json, Cargo.toml, Cargo.lock.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
User description
Summary
`finish_cancel_session_state` only cleared `state.focus_target` when the cancelled phase was not `Processing`. A Processing-phase cancel therefore left the stale window-resource handle in place until the next `begin_session_state` overwrote it. Between cancel and the next `begin_session`, any reader of `focus_target` sees a value belonging to a session that is about to be torn down.
Why this matters
Today the leak is bounded — `begin_session_state` always overwrites `focus_target` (`coordinator_state.rs:80`), and there is no documented reader that races with that interval. So this isn't user-visible today. But the asymmetry is a footgun: anyone wiring a new code path that consults `focus_target` between cancel and next session would silently inherit the stale handle.
Change
Move `state.focus_target = None` ahead of the phase check so it always fires when the cancel was accepted (i.e. not rejected for `Idle` / `Inserting` phases). Phase transition stays conditional on the original non-Processing branch — `end_session` must still own the `Processing → Idle` collapse to avoid racing with the polish/insert tail of `Processing`.
Audit linkage
Audit ID 3.3.5 (CONFIRMED). See `docs/audit-2026-05-10-validated.md` (local) for full validation context.
Test plan
Pure state-machine bookkeeping fix; no runtime behavior change in any documented path. Awaiting manual verification of cancel-during-polish on a real build.
PR Type
Bug fix, Tests
Description
Move focus_target clearing before phase check
Retain conditional phase transition for Processing
Assert focus_target cleared after accepted cancels
Assert focus_target unchanged after rejected cancels
Diagram Walkthrough
flowchart LR A["begin_cancel_session_state"] --> B{"Phase accepted?"} B -- "No (Idle/Inserting)" --> C["Return None"] B -- "Yes" --> D["finish_cancel_session_state"] D --> E["Clear focus_target"] E --> F{"Phase != Processing?"} F -- "Yes" --> G["Set phase to Idle"] F -- "No" --> H["Leave phase for end_session"]File Walkthrough
coordinator_state.rs
Clear focus_target on cancel in all phasesopenless-all/app/src-tauri/src/coordinator_state.rs
state.focus_target = Nonebefore the phase check so it clears onevery accepted cancel (including Processing)
focus_targetmust always bereleased, referencing audit 3.3.5
cancel_session_state_machine_is_table_driventest to assertfocus_targetisNoneafter any accepted cancel and remains untouchedon rejected cancels