Skip to content

macOS: reconnect fails with [Errno 2] after ~3 days, because dirhelper deletes the bundled CA file #175

Description

@trouni

Summary

On macOS the client builds its WebSocket SSL context from certifi.where(). In the one-file
PyInstaller bundle that path resolves inside the extraction directory,
/var/folders/<...>/T/_MEI<random>/certifi/cacert.pem. /usr/libexec/dirhelper, run by
/System/Library/LaunchDaemons/com.apple.bsd.dirhelper.plist at 03:35 daily with
CLEAN_FILES_OLDER_THAN_DAYS=3, deletes files under /var/folders/.../T/ that have not been
accessed in three days — including the contents of an _MEI directory belonging to a
still-running process. Apple documents the behaviour on _CS_DARWIN_USER_TEMP_DIR: files
there "may be cleaned (removed) by the system if they are not accessed in 3 days."

certifi.where() is only called on the (re)connect path, so a long-lived client reads that file
for the first time in days exactly when it needs to reconnect, and by then it is gone:

ERROR - Connection to remote host was lost. - goodbye
ERROR - Failed to connect websocket: [Errno 2] No such file or directory

Combined with the single-attempt reconnect (#174), this ends clipboard sync
permanently, with the process still running and the tray icon unchanged.

Where

utils/ssl_helper.py (3.2.0):

if PLATFORM == MACOS:
    ctx = ssl.create_default_context(cafile=certifi.where())
    return {"context": ctx}

The Linux and Windows paths return None and use the library/OS default, so they are unaffected.

Reproduce

Leave the macOS app running for more than three days without a reconnect, then break and restore
the connection. The evidence is visible without waiting, on any machine where the app has been
up that long:

$ for d in /var/folders/*/*/T/_MEI*; do
    printf '%s mtime=%s cacert=%s\n' "$d" "$(stat -f '%Sm' "$d")" \
      "$(test -f "$d/certifi/cacert.pem" && echo present || echo MISSING)"
  done
/var/folders/…/T/_MEIB8SvLX mtime=Sep  8 03:35:04 2026 cacert=MISSING
/var/folders/…/T/_MEIiqFfIl mtime=Sep  2 03:35:04 2026 cacert=MISSING
/var/folders/…/T/_MEIMvyX83 mtime=Sep 10 03:35:28 2026 cacert=MISSING

The ~03:35 mtimes are the daily purge. The app in question had started 3.2 days before its
reconnect failed.

Suggested fix

Any of these removes the failure; the first is the smallest:

  1. Resolve and read the CA bundle once at startup — e.g. load it into an SSLContext in
    __init__ and reuse that context — so the reconnect does not depend on a file that may have
    been deleted since. This does not survive a purge plus a context rebuild, but it removes
    the common case.
  2. Copy the bundle out of sys._MEIPASS into a directory macOS does not purge (e.g. under
    ~/Library/Application Support/ClipCascade/) on first run, and point cafile there.
  3. Fall back to the platform default when the file is gone:
if PLATFORM == MACOS:
    path = certifi.where()
    ctx = ssl.create_default_context(cafile=path) if os.path.isfile(path) \
        else ssl.create_default_context()
    return {"context": ctx}

Worth noting for whichever is chosen: the same reasoning applies to any other data file the
bundle reads lazily rather than at startup.

Environment

  • ClipCascade 3.2.0, macOS build (ClipCascade-Apple_macOS.ARM_M-Series.zip), Apple silicon,
    launched at login and left running for days.
  • Server 0.7.0, self-hosted, reached over wss:// through a reverse proxy with a public CA
    certificate (no ssl_ca_bundle configured, so the certifi branch is the one taken).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions