macOS Golden Gate 27.0 Beta 2: Fixing appstoreagent at 100% CPU with SIGSTOP

2026-07-04

1. Symptoms

After upgrading to macOS Golden Gate 27.0 Developer Beta 2 (Build 26A5368g), my MacBook began running extremely hot and the battery drained rapidly. I checked the process status in Terminal:

$ ps aux | grep appstoreagent
<username>       19002 107.0  0.1 488771680  25088   ??  R    12:20AM  14:55.36 /System/Library/PrivateFrameworks/AppStoreDaemon.framework/Support/appstoreagent

Even though the App Store app was not open, the appstoreagent daemon continuously consumed more than 100% of one CPU core. Force-quitting it did not help: the process was relaunched immediately and returned to full load.


2. Tracing the Logs and Finding the Root Cause

To identify the actual failure, I queried macOS Unified Logging for messages from this process:

$ /usr/bin/log show --predicate 'process == "appstoreagent" and eventMessage contains "recent_launch_times"' --last 5m

The command produced the following error:

2026-07-04 00:34:55.462135+0800 0x7969e    Error       0x0                  19002  0    appstoreagent: (libsqlite3.dylib) [com.apple.libsqlite3:logging] no such column: active_launch_events.recent_launch_times in "SELECT active_launch_events.ROWID, active_launch_events.bundle_id, active_launch_events.containing_bundle_id, active_launch_events.event_source, active_launch_events.is_extension, active_launch_events.launch_end_time, active_launch_events.launch_start_time, active_launch_events.recent_launch_times, active_launch_events.timestamp, active_launch_events.payload FROM active_launch_events"

What the error indicates

  • Root cause: while macOS Golden Gate 27.0 Beta 2 builds or migrates the security database schema in memory, its upgrade migration appears to omit the recent_launch_times column from the physical active_launch_events table.
  • Why CPU usage spikes: because the column is missing, the underlying SQLite query repeatedly throws a no such column error. After catching the error, appstoreagent immediately retries at high frequency with no delay, creating a query-fail-retry loop that saturates one CPU core.

3. Why a Normal Kill Does Not Fix It

On macOS, appstoreagent is a user agent managed by the system’s root launchd daemon.

When you force-quit it with kill -9:

  1. The process exits and briefly releases the CPU.
  2. launchd detects that the managed service exited unexpectedly and immediately relaunches it.
  3. The new appstoreagent instance runs the same database initialization query, hits the missing-schema error again, and returns to 100% CPU usage.

Force-quitting the process therefore creates a repeated launch-crash-relaunch cycle, adding system overhead and generating large volumes of logs.


4. A SIGSTOP / SIGCONT Workaround

Because the system’s read-only volume is protected by SIP (System Integrity Protection), a regular user cannot directly repair the underlying database schema. A temporary behavioral workaround is therefore the practical option.

The strategy is to control the process with signals and suspend it.

  • Send SIGSTOP: this places the process in the T (stopped) state. macOS stops scheduling CPU time for it, so CPU usage immediately falls to 0%. Crucially, launchd still considers the process alive and does not relaunch it.
  • Send SIGCONT: when you need the App Store, this signal resumes the process immediately so downloads and updates can work normally.

The following Python 3 daemon, guardian.py, automates this behavior:

#!/usr/bin/env python3
import subprocess
import time
import os
import sys
import signal

def get_pids(process_name):
    """Return all PIDs for the specified process name."""
    try:
        output = subprocess.check_output(["pgrep", "-x", process_name], text=True)
        return [int(pid.strip()) for pid in output.strip().split("\n") if pid.strip()]
    except subprocess.CalledProcessError:
        return []

def is_app_store_running():
    """Check whether the App Store GUI client is running."""
    return len(get_pids("App Store")) > 0

def get_process_state(pid):
    """Return the process state. 'T' means stopped; other values mean running."""
    try:
        output = subprocess.check_output(["ps", "-o", "state", "-p", str(pid)], text=True)
        lines = output.strip().split("\n")
        if len(lines) > 1:
            return lines[1].strip()
    except subprocess.CalledProcessError:
        pass
    return None

def main():
    print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] appstoreagent guardian daemon started.")

    while True:
        try:
            agent_pids = get_pids("appstoreagent")
            app_store_active = is_app_store_running()

            for pid in agent_pids:
                state = get_process_state(pid)
                if not state:
                    continue

                # Resume appstoreagent when the App Store is open and the process is stopped.
                if app_store_active:
                    if 'T' in state:
                        print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] App Store is running. Resuming appstoreagent (PID {pid})...")
                        os.kill(pid, signal.SIGCONT)
                # Suspend appstoreagent when the App Store is closed and the process is running.
                else:
                    if 'T' not in state:
                        print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] App Store is closed. Suspending appstoreagent (PID {pid}) to save CPU...")
                        os.kill(pid, signal.SIGSTOP)

        except Exception as e:
            print(f"Error in guardian loop: {e}", file=sys.stderr)

        time.sleep(5)

if __name__ == "__main__":
    main()

5. Running the Guardian Silently with launchd

To keep the guardian running silently in the background, package it as a macOS Launch Agent.

5.1 Create the Configuration File

Create com.user.appstoreagent-guardian.plist locally:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.user.appstoreagent-guardian</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/python3</string>
        <string>-u</string>
        <string>/path/to/guardian.py</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/path/to/guardian.log</string>
    <key>StandardErrorPath</key>
    <string>/path/to/guardian.err</string>
</dict>
</plist>

[!WARNING] Replace /path/to/guardian.py and the other example paths with absolute paths on your Mac. The -u option is strongly recommended so Python writes unbuffered output to guardian.log immediately.

5.2 Load and Activate the Service

  1. Make the guardian script executable: chmod +x /path/to/guardian.py
  2. Copy the configuration file into the user’s LaunchAgents directory: cp com.user.appstoreagent-guardian.plist ~/Library/LaunchAgents/
  3. Load and bootstrap the guardian with launchctl: launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.user.appstoreagent-guardian.plist
  4. Verify the service status: launchctl print gui/$(id -u)/com.user.appstoreagent-guardian

If the output shows state = running and the expected PID, deployment succeeded. While the App Store is closed, appstoreagent should appear as T (stopped) in ps aux, with CPU usage holding at 0.0%. Opening the App Store immediately wakes the process so it can provide normal service.

5.3 Remove the Service

After Apple ships a later beta that fixes the schema-migration bug, remove this temporary guardian with the following commands:

# Stop the background service
launchctl bootout gui/$(id -u)/com.user.appstoreagent-guardian

# Remove the configuration file
rm ~/Library/LaunchAgents/com.user.appstoreagent-guardian.plist
©2026ye-yu.com All Rights Reserved.