Menu

#412 fix(oauth): let users cancel a stuck jira/trello browser OAuth attempt

closed
nobody
None
2026-07-09
2026-07-08
Anonymous
No

Originally created by: adityaharishch

Summary

  • Closing the OAuth browser tab mid-flow left the tray's in-flight guard held for the full 5-minute CONSENT_TIMEOUT, so "Try again" (a pure UI no-op) just re-hit "already in progress"
  • Switches the spawned OAuth task to tokio::spawn, stores its JoinHandle, and adds a cancel_oauth command that aborts it + clears the flag immediately
  • Wires this into "Try again", unmount-while-waiting, and a new "Not going through? Cancel" link for the case where Atlassian's own consent screen blocks (e.g. no Jira site access) and never redirects back at all

Stacked on prior PRs in this chain.

Test plan

  • [x] cargo fmt --check, cargo build -p meridian-tray, cargo clippy -p meridian-tray -- -D warnings, cargo test -p meridian-tray -p meridian-oauth

Related

Tickets: #415

Discussion

  • Anonymous

    Anonymous - 2026-07-08

    Originally posted by: coderabbitai[bot]

    [!IMPORTANT]

    Review skipped

    Auto reviews are disabled on base/target branches other than the default branch.

    Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.


    ⚙️ Run configuration

    Configuration used: Organization UI

    Review profile: ASSERTIVE

    Plan: Pro Plus

    Run ID: c22470ba-22bf-4f99-8700-496d1790c4b1

    You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

    Use the checkbox below for a quick retry:
    - [ ] 🔍 Trigger review

    ✨ Finishing Touches
    🧪 Generate unit tests (beta) - [ ] Create PR with unit tests - [ ] Commit unit tests in branch `fix/jira-oauth-stuck-cancel`

    Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

    ❤️ Share - [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai) - [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai) - [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai) - [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

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

     
  • Anonymous

    Anonymous - 2026-07-08

    Originally posted by: Akarsh-Hegde

    Review — cancel a stuck jira/trello browser OAuth attempt

    Summary: Switches the spawned jira/trello OAuth task to a tokio::spawn whose JoinHandle is stored, so a new cancel_oauth command can .abort() it and clear the in-flight guard immediately; wired into "Try again", unmount-while-waiting, and a new manual "Cancel" link.

    Blocking / correctness issues

    1. cancel_oauth's argument shape doesn't match how mutate invokes it — the command always fails silently, so the fix never actually runs.

    mutate invokes commands as invoke(command, { body }) — it wraps the payload under a body key (ui/lib/bridge.ts:105, and the doc comment right below it). That's why every sibling write command takes a struct: start_oauth(body: StartOAuthBody), disconnect_integration(body: DisconnectBody), save_integration_token(body: SaveTokenBody).

    But the new command is pub async fn cancel_oauth(provider: String), called via mutate('/api/auth/oauth/cancel', 'cancel_oauth', { provider })invoke('cancel_oauth', { body: { provider } }). Tauri looks for a top-level provider key, finds only body, and rejects with a missing-argument error.

    Failure scenario: user closes the OAuth tab → clicks "Try again" → cancelAndReset fires cancelOAuth (fire-and-forget, .catch(() => {})), so the rejection is swallowed. The backend cancel_oauth never executes → JIRA_OAUTH_IN_FLIGHT stays true for the full 5-min CONSENT_TIMEOUT. The UI resets to idle, the user clicks Connect, and start_oauth returns "jira OAuth is already in progress" — i.e. the exact bug this PR set out to fix, now hidden behind a UI that looks reset. Rust tests + cargo clippy can't catch it (JS↔Tauri arg-shape mismatch, no TS tests).

    Fix: give it a body: CancelOAuthBody { provider: String } struct like the other mutate-invoked commands (preferred), or invoke it via a raw invoke('cancel_oauth', { provider }) the way get_oauth_status(provider: String) is called. Please exercise the real cancel→retry flow in a packaged/dev build before merge.

    Suggestions / nits

    • Latent abort-the-wrong-task race (becomes live once [#1] is fixed). In the error state in_flight is already false and a completed JoinHandle still sits in the slot. cancelAndReset fires cancelOAuth fire-and-forget, then Connect spawns a fresh task and overwrites the slot. If the earlier cancel_oauth lands after that store, it .take()s and .abort()s the brand-new live task. Narrow, but real — either skip cancelOAuth when already in error state (nothing to free), or key the abort to a generation/id.
    • Comment says "3 min", actual timeout is 5 min. The new "Not going through? Cancel" JSX comment says "for up to 3 min", but CONSENT_TIMEOUT is Duration::from_secs(300) = 5 min (meridian-oauth/src/flow.rs:52), and cancel_oauth's own doc comment correctly says 5 min. Align them.
    • Completed handle lingers in the Mutex slot until the next start/cancel — harmless (.abort() on a finished task is a no-op); could *handle_slot.lock().unwrap() = None on completion for tidiness.
    • handle_slot.lock().unwrap() uses unwrap() (discouraged outside tests); critical sections are trivial so poisoning is effectively impossible — just noting it against the convention.

    Verified correct

    • cancel_oauth registered in lib.rs's invoke_handler!; custom app commands aren't ACL-gated in Tauri 2, so no capabilities/default.json change needed (consistent with start_oauth/get_oauth_status).
    • File header on line 1; command has #[tracing::instrument] + structured tracing::info!(provider = …).
    • Ordering inside cancel_oauth (take+abort handle, then clear in_flight) correctly prevents a normal Try-again→Connect from aborting the retry task. statusRef mirror to dodge the stale-closure in the [tracker.id] cleanup is sound.

    Verdict: Needs changes — blocking issue [#1] defeats the PR's entire purpose at runtime while compiling and passing all listed checks. Small fix, but must be corrected and manually verified end-to-end before merge.

    🤖 Automated review via Claude Code

     

    Related

    Tickets: #1

  • Anonymous

    Anonymous - 2026-07-09

    Ticket changed by: adityaharishch

    • status: open --> closed
     

Log in to post a comment.