Skip to content
⌘K

ClickUp auto-update suppression on managed Macs

Updated by 2Fifteen Tech

How to take control of ClickUp updates on your managed Mac fleet using audit and remediation scripts.

The problem

ClickUp is built on ToDesktop (Electron + Squirrel.Mac). Unlike most enterprise apps, there are no MDM configuration profile keys to disable its auto-updater. This has been a known gap since Squirrel.Mac issue #192 (open since 2016).

If left unchecked, ClickUp will auto-update itself outside of your managed software pipeline, bypassing version control and potentially breaking compatibility or compliance.

What doesn't work

Before going down the same rabbit holes, here's what we ruled out:

ApproachWhy it fails
MDM configuration profile keysToDesktop apps don't read any admin-managed preferences for update control
DISABLE_SYSTEM_UPDATES env varOnly works when the app is launched from a shell with the var set, so useless for Dock/Finder launches
Blocking download.todesktop.comBlocks ALL ToDesktop apps (Linear, Cursor, etc.) and users still see failed update UI
Removing Squirrel.frameworkHigh risk of crashing the app entirely

What does work

The electron-builder maintainer recommends removing the app-update.yml file from inside the app bundle. This is the electron-updater config that tells the app where to check for updates. Without it:

  1. electron-updater has no server URL, so no update check ever fires
  2. Squirrel ShipIt is never triggered, so the "Install Update Helper" prompt never appears
  3. Your MDM becomes the sole update mechanism

The scripts

We use an audit + remediation pair deployed as an Iru Custom Script, running every 15 minutes.

Audit script (audit-clickup-update-suppression.sh)

The audit checks three conditions and exits 0 (compliant) or 1 (needs remediation):

  1. app-update.yml does not exist: confirms the updater config has been disabled
  2. App bundle is owned by root:wheel: prevents Squirrel from writing to the bundle as a standard user
  3. App bundle is not group-writable: additional write protection

If ClickUp isn't installed, the audit exits 0 (nothing to do).

#!/bin/bash # Audit Script: Check if ClickUp auto-update is suppressed # # Checks three requirements for ClickUp update suppression: # 1. app-update.yml must NOT exist (electron-updater config removed/renamed) # 2. App bundle must be owned by root:wheel (prevents Squirrel self-update) # 3. App bundle must not be group-writable (prevents standard user writes) # # Exit 0 = compliant (no remediation needed) # Exit 1 = not compliant (remediation will run) APP_PATH="/Applications/ClickUp.app" APP_UPDATE_YML="$APP_PATH/Contents/Resources/app-update.yml" # If ClickUp is not installed, nothing to audit if [[ ! -d "$APP_PATH" ]]; then echo "ClickUp is not installed. Nothing to audit." exit 0 fi FAIL=0 # 1. Check that app-update.yml does NOT exist (should be renamed to .disabled) if [[ -f "$APP_UPDATE_YML" ]]; then echo "FAIL: app-update.yml exists (auto-update is active)" ((FAIL++)) else echo "PASS: app-update.yml is absent (auto-update is suppressed)" fi # 2. Check ownership is root:wheel OWNER=$(stat -f "%Su:%Sg" "$APP_PATH" 2>/dev/null) if [[ "$OWNER" == "root:wheel" ]]; then echo "PASS: App bundle ownership is root:wheel" else echo "FAIL: App bundle ownership is $OWNER (expected root:wheel)" ((FAIL++)) fi # 3. Check that the app bundle is not group-writable PERMS=$(stat -f "%Sp" "$APP_PATH" 2>/dev/null) if [[ "$PERMS" == *"w"*"w"* ]]; then # Check specifically the group write bit (character 6: drwxrwxr-x) GROUP_WRITE=$(stat -f "%Lp" "$APP_PATH" 2>/dev/null) if (( (GROUP_WRITE / 10 % 10) & 2 )); then echo "FAIL: App bundle is group-writable ($PERMS)" ((FAIL++)) else echo "PASS: App bundle is not group-writable" fi else echo "PASS: App bundle is not group-writable" fi echo "" if [[ $FAIL -eq 0 ]]; then echo "COMPLIANT: ClickUp auto-update is suppressed." exit 0 else echo "NOT COMPLIANT: $FAIL check(s) failed. Remediation required." exit 1 fi

Remediation script (remediate-clickup-update-suppression.sh)

When the audit fails, remediation runs automatically and does three things:

  1. Renames app-update.yml to app-update.yml.disabled: disables electron-updater without deleting the file (easy to reverse if needed)
  2. Sets ownership to root:wheel with 755 permissions on the entire app bundle: locks out Squirrel's self-update writes
  3. Clears update caches for all users: removes ~/Library/Caches/desktop-updater and ~/Library/Caches/com.clickup.desktop-app.ShipIt so there's no stale update state hanging around
#!/bin/bash # Remediation Script: Suppress ClickUp auto-update mechanism # # Performs three actions: # 1. Renames app-update.yml to app-update.yml.disabled inside the app bundle # 2. Sets app bundle ownership to root:wheel with 755 permissions # 3. Cleans up electron-updater and ShipIt caches for all users APP_PATH="/Applications/ClickUp.app" APP_UPDATE_YML="$APP_PATH/Contents/Resources/app-update.yml" # If ClickUp is not installed, nothing to remediate if [[ ! -d "$APP_PATH" ]]; then echo "ClickUp is not installed. Nothing to remediate." exit 0 fi ERRORS=0 # 1. Disable electron-updater by renaming its config file if [[ -f "$APP_UPDATE_YML" ]]; then if mv "$APP_UPDATE_YML" "${APP_UPDATE_YML}.disabled"; then echo "OK: Renamed app-update.yml to app-update.yml.disabled" else echo "ERROR: Failed to rename app-update.yml" ((ERRORS++)) fi else echo "OK: app-update.yml already absent" fi # 2. Set ownership to root:wheel and permissions to 755 echo "Setting ownership to root:wheel and permissions to 755..." if chown -R root:wheel "$APP_PATH" && chmod -R 755 "$APP_PATH"; then echo "OK: Ownership and permissions set" else echo "ERROR: Failed to set ownership/permissions" ((ERRORS++)) fi # 3. Clean up electron-updater and ShipIt caches for all users for user_home in /Users/*/; do # Skip system-level directories username=$(basename "$user_home") if [[ "$username" == "Shared" || "$username" == ".localized" ]]; then continue fi # electron-updater cache (updaterCacheDirName from app-update.yml) rm -rf "${user_home}Library/Caches/desktop-updater" 2>/dev/null # Squirrel ShipIt cache rm -rf "${user_home}Library/Caches/com.clickup.desktop-app.ShipIt" 2>/dev/null done echo "OK: Cleared update caches for all users" echo "" if [[ $ERRORS -eq 0 ]]; then echo "SUCCESS: ClickUp auto-update suppressed." exit 0 else echo "FAILED: $ERRORS error(s) during remediation." exit 1 fi

Every 15 minutes

When your MDM pushes a new version of ClickUp via Auto App, the fresh app bundle restores the original app-update.yml. The 15-minute audit cadence ensures the remediation re-suppresses the updater promptly after any managed update.

Code signature impact

Renaming app-update.yml invalidates the app's code signature (it's a sealed resource). This is not a problem for MDM-deployed apps because:

  • MDM-installed apps have no quarantine attribute, so Gatekeeper doesn't re-check them
  • macOS does not continuously re-verify sealed resources for already-trusted apps
  • This is a well-established technique used by Mac admins for enterprise app management

Deployment steps (Iru)

  1. Create a new Custom Script in Iru
  2. Upload audit-clickup-update-suppression.sh as the audit script
  3. Upload remediate-clickup-update-suppression.sh as the remediation script
  4. Set the run interval to every 15 minutes
  5. Assign to the appropriate blueprint(s)

That's it. Once deployed, ClickUp updates are fully managed through your MDM pipeline and users will never see the Squirrel update prompt.