Skip to content

Comments

Fix event hooks not persisting across server syncs#799

Merged
gsxdsm merged 2 commits intoAutoMaker-Org:v0.15.0rcfrom
gsxdsm:fix/event-hook-persistence
Feb 22, 2026
Merged

Fix event hooks not persisting across server syncs#799
gsxdsm merged 2 commits intoAutoMaker-Org:v0.15.0rcfrom
gsxdsm:fix/event-hook-persistence

Conversation

@gsxdsm
Copy link
Collaborator

@gsxdsm gsxdsm commented Feb 22, 2026

Summary

  • Add event hooks to the empty array overwrite guard in settings service to prevent loss during sync
  • Implement event hooks merging logic to preserve local settings when server has no hooks
  • Add selective reconciliation of event hooks from server during fast hydration
  • Make setEventHooks async to ensure server persistence when hooks are updated

Changes

  • settings-service.ts: Added eventHooks to the ignoreEmptyArrayOverwrite guard to prevent empty arrays from overwriting existing hooks during settings updates
  • use-settings-migration.ts: Added logic to preserve event hooks from localStorage when server settings are empty, ensuring user hooks aren't lost during migration
  • __root.tsx: Added selective reconciliation of event hooks from server during fast hydrate. Only updates store if server has hooks and they differ from current state, preventing unnecessary re-renders
  • app-store.ts: Modified setEventHooks to be async and automatically persist changes to the server via HTTP API, with error logging on failure
  • state-types.ts: Updated setEventHooks type signature from void to Promise<void> to reflect async behavior
  • settings.ts: Added eventHooks: [] to DEFAULT_GLOBAL_SETTINGS to ensure proper default initialization

Summary by CodeRabbit

  • Bug Fixes

    • Prevented accidental clearing of event hooks during settings sync and improved preservation when merging local and server settings.
  • Improvements

    • Reconciled event hooks from the server without forcing unrelated UI updates.
    • Event hooks now persist to the server automatically when edited.
  • New Features

    • User-facing error reporting (toasts) and logging for create/update/delete actions on event hooks.
  • Defaults

    • Global settings now include an explicit default for event hooks.

@coderabbitai
Copy link

coderabbitai bot commented Feb 22, 2026

📝 Walkthrough

Walkthrough

Adds an explicit allowEmptyEventHooks flag to server settings updates to permit clearing eventHooks; client migration preserves local hooks when server has none; store's setEventHooks now async and persists to server; root reconciles server hooks post-hydration; default global settings include eventHooks: [].

Changes

Cohort / File(s) Summary
Server settings update
apps/server/src/services/settings-service.ts
Introduce allowEmptyEventHooks flag on updateGlobalSettings; when true allows clearing eventHooks with an empty array; flag removed before persistence; conditionalize existing empty-array guard.
Client migration & reconciliation
apps/ui/src/hooks/use-settings-migration.ts, apps/ui/src/routes/__root.tsx
Merge logic preserves local eventHooks if server has none; post-hydration root reconciles and selectively updates store with server eventHooks only when non-empty and different.
Store API sync
apps/ui/src/store/app-store.ts, apps/ui/src/store/types/state-types.ts
setEventHooks changed to async: updates local state immediately, then calls HTTP API to persist eventHooks; errors are logged (no throw). Action signature updated to return Promise<void>.
Defaults
libs/types/src/settings.ts
Add eventHooks: [] to DEFAULT_GLOBAL_SETTINGS.
UI event-hooks UX
apps/ui/src/components/.../event-hooks-section.tsx
Handlers converted to async with try/catch; add logging and toast error notifications for create/update/delete/toggle flows.

Sequence Diagram

sequenceDiagram
    actor User
    participant Root as Root Component
    participant Store as App Store
    participant API as Server API

    User->>Root: Open app / navigate
    Root->>Store: Trigger fast-hydrate
    Store->>API: Fetch cached/final settings
    API-->>Store: Return finalSettings (may include eventHooks)
    Store-->>Root: Hydrated state

    Root->>Root: Compare serverHooks vs storeHooks (JSON.stringify)
    alt serverHooks non-empty AND differ
        Root->>Store: set eventHooks from server
    else server empty AND local has hooks
        Root->>Store: keep local eventHooks (migration preserves)
    end

    User->>Store: Call setEventHooks(newHooks)
    Store->>Store: Update local state immediately
    Store->>API: POST updateGlobal({ eventHooks: newHooks, allowEmptyEventHooks: ... })
    API-->>Store: Success or error (error logged)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Suggested labels

Enhancement

Poem

🐇 I hop between client and server tonight,

Allowing hooks to vanish when flagged right,
I keep local memories snug and tight,
Sync softly, log boldly, and toast with delight,
A little rabbit cheer for settings made light.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective of the PR: fixing event hooks persistence across server syncs, which is the central theme of all changes across six files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

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

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @gsxdsm, 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 significantly improves the robustness and persistence of event hooks across client-server synchronizations. It addresses potential data loss scenarios by safeguarding against empty array overwrites, prioritizing local settings during merges, and intelligently reconciling server-side changes during hydration. The changes ensure that event hook updates are reliably saved to the server, enhancing the overall stability of user configurations.

Highlights

  • Event Hook Persistence: Added eventHooks to the empty array overwrite guard in the settings service to prevent data loss during server synchronization.
  • Local Settings Preservation: Implemented merging logic for event hooks to preserve local settings when the server does not have any hooks defined.
  • Selective Reconciliation: Introduced selective reconciliation of event hooks from the server during fast hydration, updating only when server hooks are present and differ from the current state.
  • Asynchronous Updates: Modified setEventHooks to be asynchronous, ensuring server persistence when event hooks are updated and including error logging for failed syncs.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • apps/server/src/services/settings-service.ts
    • Added 'eventHooks' to the 'ignoreEmptyArrayOverwrite' guard to prevent accidental data loss.
  • apps/ui/src/hooks/use-settings-migration.ts
    • Implemented logic to preserve 'eventHooks' from local storage if server settings are empty during migration.
  • apps/ui/src/routes/__root.tsx
    • Added selective reconciliation for 'eventHooks' during fast hydration, updating the store only if server hooks differ and are not empty.
  • apps/ui/src/store/app-store.ts
    • Modified 'setEventHooks' to be asynchronous and persist changes to the server via HTTP API, with error logging.
  • apps/ui/src/store/types/state-types.ts
    • Updated the type signature of 'setEventHooks' from 'void' to 'Promise' to reflect its new asynchronous behavior.
  • libs/types/src/settings.ts
    • Initialized 'eventHooks' as an empty array in 'DEFAULT_GLOBAL_SETTINGS' for proper default setup.
Activity
  • No specific activity has been recorded for this pull request yet.
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 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 counter productive. 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.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

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
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 aims to fix persistence issues for event hooks by adding server-side guards, migration logic, and making the store action sync with the server. The changes are logical and follow existing patterns. However, I've identified a significant issue where the new guards prevent users from intentionally deleting all their event hooks, leading to a confusing user experience where deletions are silently reverted. I've left two high-severity comments on the server-side and client-side code that contribute to this issue, with suggestions for resolution. The rest of the changes look good.

ignoreEmptyArrayOverwrite('mcpServers');
ignoreEmptyArrayOverwrite('enabledCursorModels');
ignoreEmptyArrayOverwrite('claudeApiProfiles');
ignoreEmptyArrayOverwrite('eventHooks');
Copy link
Contributor

Choose a reason for hiding this comment

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

high

While adding eventHooks to ignoreEmptyArrayOverwrite prevents accidental data loss from an uninitialized client, it also introduces a bug where a user cannot intentionally delete all their event hooks. An attempt to set eventHooks to an empty array will be ignored by the server, and the hooks will reappear on the next application load.

Given that other parts of the client-side logic (like use-settings-migration) seem to handle data preservation during initialization, this server-side guard might be too aggressive.

Consider removing this line to allow users to clear all their event hooks. A similar property, claudeCompatibleProviders, is intentionally not guarded for this reason, as noted in the comment on line 575.

Comment on lines +604 to +605
JSON.stringify(serverHooks) !== JSON.stringify(currentHooks) &&
serverHooks.length > 0
Copy link
Contributor

Choose a reason for hiding this comment

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

high

In conjunction with my comment on settings-service.ts, this serverHooks.length > 0 check should be removed. It prevents the client from syncing an empty eventHooks array from the server (e.g., when a user deletes all hooks on another device). This can lead to data inconsistencies where this client retains stale hooks and may even re-sync them back to the server later.

Removing this check will ensure that deleting all hooks is correctly propagated across clients.

                    JSON.stringify(serverHooks) !== JSON.stringify(currentHooks)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/server/src/services/settings-service.ts (1)

519-575: ⚠️ Potential issue | 🟠 Major

Allow intentional clearing of eventHooks (Line 574).
With this guard in place, any update that sets eventHooks: [] is discarded when existing hooks are present, so users can’t delete all hooks (they’ll reappear on the next sync). Consider an explicit “allow empty” escape hatch so intentional clears can pass through.

💡 Example approach (explicit clear flag)
+    const allowEmptyEventHooks =
+      (updates as Record<string, unknown>).__allowEmptyEventHooks === true;
+    if (allowEmptyEventHooks) {
+      delete (sanitizedUpdates as Record<string, unknown>).__allowEmptyEventHooks;
+    }

-    const ignoreEmptyArrayOverwrite = <K extends keyof GlobalSettings>(key: K): void => {
+    const ignoreEmptyArrayOverwrite = <K extends keyof GlobalSettings>(
+      key: K,
+      opts: { allowEmpty?: boolean } = {}
+    ): void => {
       const nextVal = sanitizedUpdates[key] as unknown;
       const curVal = current[key] as unknown;
       if (
-        Array.isArray(nextVal) &&
+        !opts.allowEmpty &&
+        Array.isArray(nextVal) &&
         nextVal.length === 0 &&
         Array.isArray(curVal) &&
         curVal.length > 0
       ) {
         delete sanitizedUpdates[key];
       }
     };

-    ignoreEmptyArrayOverwrite('eventHooks');
+    ignoreEmptyArrayOverwrite('eventHooks', { allowEmpty: allowEmptyEventHooks });

Then, when the UI intentionally clears hooks (e.g., setEventHooks([])), pass __allowEmptyEventHooks: true in the update.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/services/settings-service.ts` around lines 519 - 575, The
empty-array guard (ignoreEmptyArrayOverwrite) prevents intentional clearing of
eventHooks by deleting sanitizedUpdates.eventHooks when current hooks exist;
modify the update flow to honor an explicit escape hatch: treat a boolean flag
like sanitizedUpdates.__allowEmptyEventHooks as permission to allow an empty
eventHooks array (i.e., skip calling ignoreEmptyArrayOverwrite('eventHooks')
when that flag is true), and ensure you remove/delete the __allowEmptyEventHooks
flag from sanitizedUpdates before persisting so it isn’t stored. Reference:
ignoreEmptyArrayOverwrite, sanitizedUpdates, eventHooks, and the new
__allowEmptyEventHooks flag.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/ui/src/store/types/state-types.ts`:
- Line 637: Update the four handlers in
apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx
(handleDeleteHook, handleToggleHook, handleSaveHook) so they properly await and
handle the Promise returned by setEventHooks (now typed as () => Promise<void>):
either mark each handler async and use await setEventHooks(...) inside try/catch
to surface/log errors, or keep them synchronous but chain
setEventHooks(...).catch(...) to handle failures; ensure you reference the
setEventHooks calls in those handler functions and add consistent error
handling/logging.

---

Outside diff comments:
In `@apps/server/src/services/settings-service.ts`:
- Around line 519-575: The empty-array guard (ignoreEmptyArrayOverwrite)
prevents intentional clearing of eventHooks by deleting
sanitizedUpdates.eventHooks when current hooks exist; modify the update flow to
honor an explicit escape hatch: treat a boolean flag like
sanitizedUpdates.__allowEmptyEventHooks as permission to allow an empty
eventHooks array (i.e., skip calling ignoreEmptyArrayOverwrite('eventHooks')
when that flag is true), and ensure you remove/delete the __allowEmptyEventHooks
flag from sanitizedUpdates before persisting so it isn’t stored. Reference:
ignoreEmptyArrayOverwrite, sanitizedUpdates, eventHooks, and the new
__allowEmptyEventHooks flag.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx (2)

51-66: Optionally use toast.promise for save to provide loading feedback.

handleSaveHook involves a server round-trip that may have noticeable latency. Replacing the manual try/catch with toast.promise gives the user a loading indicator at no extra code cost:

✨ Optional refactor using toast.promise
  const handleSaveHook = async (hook: EventHook) => {
-   try {
-     if (editingHook) {
-       await setEventHooks(eventHooks.map((h) => (h.id === hook.id ? hook : h)));
-     } else {
-       await setEventHooks([...eventHooks, hook]);
-     }
-     setDialogOpen(false);
-     setEditingHook(null);
-   } catch (error) {
-     logger.error('Failed to save event hook:', error);
-     toast.error('Failed to save event hook');
-   }
+   const savePromise = editingHook
+     ? setEventHooks(eventHooks.map((h) => (h.id === hook.id ? hook : h)))
+     : setEventHooks([...eventHooks, hook]);
+
+   toast.promise(savePromise, {
+     loading: 'Saving hook…',
+     success: () => {
+       setDialogOpen(false);
+       setEditingHook(null);
+       return 'Event hook saved';
+     },
+     error: (err) => {
+       logger.error('Failed to save event hook:', err);
+       return 'Failed to save event hook';
+     },
+   });
  };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx`
around lines 51 - 66, Replace the manual try/catch in handleSaveHook with a
toast.promise wrapper to surface loading/success/failure UI states; call
toast.promise around the async setEventHooks(...) call(s) (both the update path
in handleSaveHook and the add-new path) passing promises that perform
setEventHooks, provide appropriate messages for pending/success/error, and keep
the existing state updates (setDialogOpen(false); setEditingHook(null)) after
the awaited promise resolves; ensure logger.error and toast.error are still
invoked on rejection if you need extra logging.

33-49: Consider adding an in-flight guard to prevent concurrent stale-closure writes.

All three handlers derive the new state by transforming the eventHooks value captured in the render-time closure. With no loading/pending gate, a second action fired before the first await setEventHooks(...) resolves operates on a stale snapshot and will overwrite the first write. For example, a rapid delete-then-toggle can silently resurrect the deleted hook.

A simple isPending flag that disables the delete/toggle controls is the lightest fix:

♻️ Suggested guard pattern
+  const [isPending, setIsPending] = useState(false);

   const handleDeleteHook = async (hookId: string) => {
+    if (isPending) return;
+    setIsPending(true);
     try {
       await setEventHooks(eventHooks.filter((h) => h.id !== hookId));
     } catch (error) {
       logger.error('Failed to delete event hook:', error);
       toast.error('Failed to delete event hook');
+    } finally {
+      setIsPending(false);
     }
   };

   const handleToggleHook = async (hookId: string, enabled: boolean) => {
+    if (isPending) return;
+    setIsPending(true);
     try {
       await setEventHooks(eventHooks.map((h) => (h.id === hookId ? { ...h, enabled } : h)));
     } catch (error) {
       logger.error('Failed to toggle event hook:', error);
       toast.error('Failed to update event hook');
+    } finally {
+      setIsPending(false);
     }
   };

Pass isPending into HookCard and disable the action buttons while pending:

  <HookCard
    key={hook.id}
    hook={hook}
    onEdit={() => handleEditHook(hook)}
    onDelete={() => handleDeleteHook(hook.id)}
    onToggle={(enabled) => handleToggleHook(hook.id, enabled)}
+   disabled={isPending}
  />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx`
around lines 33 - 49, The handlers handleDeleteHook and handleToggleHook operate
on a render-closed eventHooks snapshot and can cause stale-overwrites when calls
overlap; introduce an isPending (or similar) state, set it true before awaiting
setEventHooks and false after, pass isPending into HookCard to disable action
buttons while pending, and also switch setEventHooks calls to the functional
updater form (prev => prev.filter(...) or prev.map(...)) so updates derive from
the latest state if multiple requests happen concurrently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx`:
- Around line 33-66: The component handlers (handleDeleteHook, handleToggleHook,
handleSaveHook) call setEventHooks which currently performs an optimistic update
without rollback; add a proper rollback by saving the previous eventHooks state
before calling setEventHooks and restoring it if the server call fails, or
implement the rollback inside setEventHooks itself by capturing get().eventHooks
into a local variable, applying the optimistic set, calling
httpApi.settings.updateGlobal, and on catch restoring the saved previous state
and logging the error (ensure you reference setEventHooks in app-store.ts for
this change).

---

Nitpick comments:
In
`@apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx`:
- Around line 51-66: Replace the manual try/catch in handleSaveHook with a
toast.promise wrapper to surface loading/success/failure UI states; call
toast.promise around the async setEventHooks(...) call(s) (both the update path
in handleSaveHook and the add-new path) passing promises that perform
setEventHooks, provide appropriate messages for pending/success/error, and keep
the existing state updates (setDialogOpen(false); setEditingHook(null)) after
the awaited promise resolves; ensure logger.error and toast.error are still
invoked on rejection if you need extra logging.
- Around line 33-49: The handlers handleDeleteHook and handleToggleHook operate
on a render-closed eventHooks snapshot and can cause stale-overwrites when calls
overlap; introduce an isPending (or similar) state, set it true before awaiting
setEventHooks and false after, pass isPending into HookCard to disable action
buttons while pending, and also switch setEventHooks calls to the functional
updater form (prev => prev.filter(...) or prev.map(...)) so updates derive from
the latest state if multiple requests happen concurrently.

Comment on lines +33 to 66
const handleDeleteHook = async (hookId: string) => {
try {
await setEventHooks(eventHooks.filter((h) => h.id !== hookId));
} catch (error) {
logger.error('Failed to delete event hook:', error);
toast.error('Failed to delete event hook');
}
};

const handleToggleHook = (hookId: string, enabled: boolean) => {
setEventHooks(eventHooks.map((h) => (h.id === hookId ? { ...h, enabled } : h)));
const handleToggleHook = async (hookId: string, enabled: boolean) => {
try {
await setEventHooks(eventHooks.map((h) => (h.id === hookId ? { ...h, enabled } : h)));
} catch (error) {
logger.error('Failed to toggle event hook:', error);
toast.error('Failed to update event hook');
}
};

const handleSaveHook = (hook: EventHook) => {
if (editingHook) {
// Update existing
setEventHooks(eventHooks.map((h) => (h.id === hook.id ? hook : h)));
} else {
// Add new
setEventHooks([...eventHooks, hook]);
const handleSaveHook = async (hook: EventHook) => {
try {
if (editingHook) {
// Update existing
await setEventHooks(eventHooks.map((h) => (h.id === hook.id ? hook : h)));
} else {
// Add new
await setEventHooks([...eventHooks, hook]);
}
setDialogOpen(false);
setEditingHook(null);
} catch (error) {
logger.error('Failed to save event hook:', error);
toast.error('Failed to save event hook');
}
setDialogOpen(false);
setEditingHook(null);
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find app-store.ts
fd -t f 'app-store' --extension ts

Repository: AutoMaker-Org/automaker

Length of output: 97


🏁 Script executed:

# Search for setEventHooks implementation
rg -n 'setEventHooks' --type ts -A 30 -B 2 | head -100

Repository: AutoMaker-Org/automaker

Length of output: 9928


Implement rollback in setEventHooks on server failure, or restore prior state in each handler.

The setEventHooks implementation in app-store.ts (lines 1418–1426) uses an optimistic-update pattern: it mutates the Zustand store immediately (set({ eventHooks: hooks })) before awaiting the server call (httpApi.settings.updateGlobal). If the server rejects the change, the catch block only logs the error and does not revert the local state.

This means if the network fails or the server returns an error, the UI permanently reflects the mutation (hook deleted/toggled/added) while the server retains the original state—a divergence that persists until the next sync.

Option 1: Implement rollback in the store:

setEventHooks: async (hooks) => {
  const previous = get().eventHooks;  // Save prior state
  set({ eventHooks: hooks });
  try {
    const httpApi = getHttpApiClient();
    await httpApi.settings.updateGlobal({ eventHooks: hooks });
  } catch (error) {
    logger.error('Failed to sync event hooks:', error);
    set({ eventHooks: previous });  // Revert on error
  }
},

Option 2: Add explicit rollback in each component handler:

  const handleDeleteHook = async (hookId: string) => {
+   const previousHooks = eventHooks;
    try {
      await setEventHooks(eventHooks.filter((h) => h.id !== hookId));
    } catch (error) {
      logger.error('Failed to delete event hook:', error);
      toast.error('Failed to delete event hook');
+     await setEventHooks(previousHooks).catch(() => {});
    }
  };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx`
around lines 33 - 66, The component handlers (handleDeleteHook,
handleToggleHook, handleSaveHook) call setEventHooks which currently performs an
optimistic update without rollback; add a proper rollback by saving the previous
eventHooks state before calling setEventHooks and restoring it if the server
call fails, or implement the rollback inside setEventHooks itself by capturing
get().eventHooks into a local variable, applying the optimistic set, calling
httpApi.settings.updateGlobal, and on catch restoring the saved previous state
and logging the error (ensure you reference setEventHooks in app-store.ts for
this change).

@gsxdsm gsxdsm merged commit 1d73291 into AutoMaker-Org:v0.15.0rc Feb 22, 2026
7 checks passed
gsxdsm added a commit to gsxdsm/automaker that referenced this pull request Feb 23, 2026
* Changes from fix/event-hook-persistence

* feat: Add explicit permission escape hatch for clearing eventHooks and improve error handling in UI
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.

1 participant