cjpais/Handy · error · Error

Failed to update binding

Error message

Failed to update binding

What it means

Frontend throw in settingsStore.updateBinding when the changeBinding Tauri command rejects (empty/whitespace binding, or shortcut fails validate_shortcut_for_implementation for the selected keyboard implementation) or returns success:false (binding id not present in settings or defaults, or the OS-level register_shortcut failed). The store then rolls back its optimistic binding update.

Source

Thrown at src/stores/settingsStore.ts:370

                  [id]: {
                    ...state.settings.bindings?.[id]!,
                    current_binding: binding,
                  },
                },
              }
            : null,
        }));

        const result = await commands.changeBinding(id, binding);

        // Check if the command executed successfully
        if (result.status === "error") {
          throw new Error(result.error);
        }

        // Check if the binding change was successful
        if (!result.data.success) {
          throw new Error(result.data.error || "Failed to update binding");
        }
      } catch (error) {
        console.error(`Failed to update binding ${id}:`, error);

        // Rollback on error
        if (originalBinding && get().settings) {
          set((state) => ({
            settings: state.settings
              ? {
                  ...state.settings,
                  bindings: {
                    ...state.settings.bindings,
                    [id]: {
                      ...state.settings.bindings?.[id]!,
                      current_binding: originalBinding,
                    },
                  },
                }

View on GitHub (pinned to 98a4d80cce)

Solutions

  1. Surface result.data.error / the caught message — it names the exact backend reason
  2. On macOS, grant Accessibility permission (System Settings > Privacy & Security > Accessibility) and retry
  3. Try a different key combination to rule out an OS/app conflict
  4. Verify the binding id matches one defined in the backend defaults
  5. The store already rolled back the optimistic update — re-sync UI state from settings before retrying

Example fix

// before
const result = await commands.changeBinding(id, binding);
if (!result.data.success) throw new Error(result.data.error || 'Failed to update binding');

// after — keep the backend reason
if (result.status === 'error') throw new Error(`changeBinding rejected: ${result.error}`);
if (!result.data.success) throw new Error(result.data.error ?? `changeBinding failed for ${id}`);
Defensive patterns

Strategy: try-catch

Validate before calling

// reject obviously invalid input before the round-trip
if (!binding || binding.trim().length === 0) {
  throw new Error('Binding cannot be empty');
}
if (!DEFAULT_BINDING_IDS.includes(id)) {
  throw new Error(`Unknown binding id: ${id}`);
}
const result = await commands.changeBinding(id, binding);

Type guard

type ChangeBindingResult =
  | { status: 'error'; error: string }
  | { status: 'ok'; data: { success: true; binding: Binding; error: null } }
  | { status: 'ok'; data: { success: false; binding: null; error: string } };

function isRejected(
  r: ChangeBindingResult,
): r is Extract<ChangeBindingResult, { status: 'error' }> | Extract<ChangeBindingResult, { status: 'ok'; data: { success: false } }> {
  return r.status === 'error' || r.data.success === false;
}

Try / catch

try {
  const result = await commands.changeBinding(id, binding);
  if (isRejected(result)) {
    const reason = result.status === 'error' ? result.error : result.data.error;
    throw new Error(reason ?? `changeBinding failed for ${id}`);
  }
} catch (error) {
  // rollback (already implemented) then surface `error.message` to the UI toast
}

Prevention

When it happens

Trigger: Passing an unknown binding id (typo, removed setting) — backend returns success:false with 'Binding with id ... not found in defaults'; binding string empty or only modifiers so validation fails; register_shortcut fails at the OS layer (macOS accessibility permission missing, combo already grabbed by another app or reserved by the OS).

Common situations: macOS without Accessibility permission granted for keyboard shortcuts; hotkey conflicts with system/global shortcuts of other apps; frontend calling with an id from an older settings schema; user clearing the shortcut field.

Related errors


AI-assisted analysis of cjpais/Handy@98a4d80cce (2026-08-16). Data as JSON: /api/errors/42e5aa26649b3ce4. Report an issue: GitHub.