Skip to content

[Fix] Hoist IPC call out of notification setState updater#492

Open
mvanhorn wants to merge 1 commit into
clawwork-ai:mainfrom
mvanhorn:fix/general-section-notification-toggle-ipc-side-effect
Open

[Fix] Hoist IPC call out of notification setState updater#492
mvanhorn wants to merge 1 commit into
clawwork-ai:mainfrom
mvanhorn:fix/general-section-notification-toggle-ipc-side-effect

Conversation

@mvanhorn
Copy link
Copy Markdown
Collaborator

Summary

Closes #400.

GeneralSection.handleNotificationToggle was performing
window.clawwork.updateSettings(...) inside a setState updater. React
treats updaters as pure functions and double-invokes them in Strict Mode
(dev) to detect impurity, so every notification toggle was firing the IPC
settings persist twice -- the issue body has the full diagnosis.

Implementation

Match the fix recipe in the issue:

   const handleNotificationToggle = useCallback(
     (key: 'taskComplete' | 'approvalRequest' | 'gatewayDisconnect', value: boolean) => {
-      setNotifyState((prev) => {
-        const next = { ...prev, [key]: value };
-        window.clawwork.updateSettings({ notifications: next });
-        return next;
-      });
+      const next = { ...notifyState, [key]: value };
+      setNotifyState(next);
+      window.clawwork.updateSettings({ notifications: next });
     },
-    [],
+    [notifyState],
   );

Compute the next state once, dispatch the state update, then dispatch
the IPC call as a separate side effect. notifyState moves into the
useCallback deps so the closure stays in sync with the stored value
across re-renders.

Verification

node scripts/check-architecture.mjs            # passes
npx eslint packages/desktop/src/renderer/layouts/Settings/sections/GeneralSection.tsx  # clean
npx prettier --check packages/desktop/src/renderer/layouts/Settings/sections/GeneralSection.tsx  # clean
cd packages/desktop && npx tsc --noEmit        # clean

pnpm check was not run because corepack downloaded pnpm 11.0.9 in this
environment, which exits non-zero on every workspace-level invocation
(unrelated to this change). The individual steps that pnpm check
chains run cleanly when invoked directly via node / npx tsc / npx eslint / npx prettier. Pinning pnpm or running on the project's
expected toolchain should produce the same green result.

Notes

  • Single-file diff (4+ / 6-).
  • Stays inside renderer-only state. No protocol, IPC bridge, or store
    changes.
  • Manual confirmation in dev that only one IPC call fires per toggle is
    the user-facing acceptance from the issue's "Verification" section --
    flagging that I did not run the desktop app locally; happy to do that
    if you'd like before merge.

GeneralSection.handleNotificationToggle was performing
window.clawwork.updateSettings(...) inside a setState updater. React
treats updaters as pure functions and double-invokes them in Strict
Mode (dev) to detect impurity, so every notification toggle was
firing the IPC settings persist twice. Issue clawwork-ai#400 has the full
reproducer.

Compute the next state once, dispatch the state update, then dispatch
the IPC call as a separate effect. notifyState moves into the
useCallback deps so the closure stays in sync with the stored value
across re-renders.

Closes clawwork-ai#400.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

Hi @mvanhorn,
Thanks for your pull request!
If the PR is ready, use the /auto-cc command to assign Reviewer to Review.
We will review it shortly.

Details

Instructions for interacting with me using comments are available here.
If you have questions or suggestions related to my behavior, please file an issue against the gh-ci-bot repository.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a side-effect issue within the notification settings toggle logic. By moving the IPC communication outside of the state updater function, it ensures that settings persistence is only triggered once per interaction, preventing redundant calls caused by React's Strict Mode behavior.

Highlights

  • IPC call side-effect removal: Moved the window.clawwork.updateSettings IPC call out of the setNotifyState updater function to prevent duplicate calls in React Strict Mode.
  • Dependency management: Updated the useCallback hook dependencies to include notifyState, ensuring the closure remains synchronized with the current state.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@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 refactors the handleNotificationToggle function to move the IPC call outside of the state updater. While this prevents double-invocation in React Strict Mode, it introduces a potential race condition where rapid toggles could result in stale state overwriting updates. Feedback suggests using useEffect or a ref to safely synchronize state with the backend. Additionally, it is recommended to add error handling and success notifications to align with the component's existing patterns.

Comment on lines +101 to +105
const next = { ...notifyState, [key]: value };
setNotifyState(next);
window.clawwork.updateSettings({ notifications: next });
},
[],
[notifyState],
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.

medium

Hoisting the IPC call out of the setNotifyState updater successfully prevents double-invocation in React Strict Mode. However, this implementation introduces a potential race condition because it relies on notifyState from the closure. If a user toggles multiple settings quickly, notifyState might be stale during the second click (before the first update's re-render), causing the first update to be overwritten in the final state.

To maintain state consistency while keeping the side effect out of the updater, consider using a useEffect hook to sync state to the backend, or use a ref to track the latest state value for the IPC call.

});
const next = { ...notifyState, [key]: value };
setNotifyState(next);
window.clawwork.updateSettings({ notifications: next });
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.

medium

For consistency with other setting handlers in this component (e.g., handleThemeToggle at line 38 or handleDensityChange at line 46), consider adding a success toast after the settings are updated, and handle potential IPC errors to inform the user if the persistence fails.

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.

[Bug] GeneralSection handleNotificationToggle fires IPC call inside setState updater

1 participant