cjpais/Handy · warning

Failed to fetch secure input status:

Error message

Failed to fetch secure input status:

What it means

SecureInputWarning mounts and immediately fetches macOS Secure Input state via the getSecureInputStatus command, then subscribes to the secure-input-changed event. If the initial fetch rejects, this catch logs it; status stays null so the warning banner stays hidden until a subsequent event arrives. Because the component also listens for changes, the practical impact is limited to missing a pre-existing secure-input state at mount.

Source

Thrown at src/components/SecureInputWarning.tsx:30

/**
 * Compact warning banner shown while macOS Secure Input is stuck on.
 *
 * Secure Input (password fields, Terminal's "Secure Keyboard Entry", a stuck
 * loginwindow) blocks key events from reaching Handy's keyboard listener, so
 * keyed shortcuts silently stop firing (issue #1578). The backend monitor
 * emits `secure-input-changed` on state transitions; `sustained` filters out
 * the normal momentary activation from focusing a password field.
 */
const SecureInputWarning: React.FC = () => {
  const { t } = useTranslation();
  const [status, setStatus] = useState<SecureInputStatus | null>(null);
  const [dismissed, setDismissed] = useState(false);

  const refresh = useCallback(async () => {
    try {
      setStatus(await commands.getSecureInputStatus());
    } catch (e) {
      console.warn("Failed to fetch secure input status:", e);
    }
  }, []);

  useEffect(() => {
    refresh();
    const unlisten = listen<SecureInputStatus>(
      "secure-input-changed",
      (event) => setStatus(event.payload),
    );
    return () => {
      unlisten.then((fn) => fn());
    };
  }, [refresh]);

  // Only warn when the user is actually impacted: a binding is degraded
  // (side-specific matching widened) or dead (e.g. fn+key), or they ran into
  // the blocked shortcut recorder. When the fallback covers everything
  // transparently — and nothing else surfaced — stay silent; the backend

View on GitHub (pinned to c89b7bf389)

Solutions

  1. Re-run refresh() after the unlisten promise resolves (subscription confirmed) or on window focus to cover mount races
  2. Skip mounting the component on non-macOS platforms (it is a macOS-only concern)
  3. Guard with isTauri() for browser development
  4. Have the Rust command log its own failure cause so the warn payload identifies the real error

Example fix

// before
useEffect(() => {
  refresh();
  const unlisten = listen<SecureInputStatus>("secure-input-changed", (event) => setStatus(event.payload));
  return () => { unlisten.then((fn) => fn()); };
}, []);

// after
useEffect(() => {
  refresh();
  const unlisten = listen<SecureInputStatus>("secure-input-changed", (event) => setStatus(event.payload));
  unlisten.then(() => refresh()); // re-poll once the subscription is live
  return () => { unlisten.then((fn) => fn()); };
}, [refresh]);
Defensive patterns

Strategy: retry

Validate before calling

// Only mount on macOS, inside Tauri
{platform() === "macos" && isTauri() && <SecureInputWarning />}

Try / catch

const refresh = useCallback(async () => {
  try { setStatus(await commands.getSecureInputStatus()); }
  catch (e) { console.warn("Failed to fetch secure input status:", e); }
}, []);

Prevention

When it happens

Trigger: The invoke firing before the backend command/watcher is ready; the command erroring on non-macOS platforms if the component is mounted there; the secure-input watcher thread failing to spawn in the Rust backend; browser dev where invoke rejects.

Common situations: Mount-time races at app startup; development outside Tauri; macOS keychain/SecureInput API edge cases where the poll errors.

Related errors


AI-assisted analysis of cjpais/Handy@c89b7bf389 (2026-08-17). Data as JSON: /api/errors/de2eed336789b2eb. Report an issue: GitHub.