Skip to main content
The Ops Playbook

Stopping Login Keychain Prompt Loops Before They Escalate

Automate detection and self-service repair of macOS login keychain sync failures so technicians stop closing the same credential prompt ticket weekly.

Stopping Login Keychain Prompt Loops Before They Escalate
Emi NakamuraEmi Nakamura10 min readTier L135 min

This playbook covers

Share

#The Old Way vs the New Way

The old way looks like this: a user changes their network account password through the identity provider, and by lunchtime they are opening three, four, sometimes ten dialog boxes a day asking them to allow Mail, Safari, or Slack to use the login keychain. The L1 fix has historically been a remote session where the technician walks the user through System Settings, deletes the login keychain, and lets macOS rebuild it – usually losing saved Wi-Fi passwords, certificates, and app tokens in the process. It is slow, it is destructive, and it repeats itself every password rotation cycle for the same population of users.

The new way treats keychain drift as a telemetry signal, not a helpdesk conversation. We detect the unlock failure before the user files a ticket, and we remediate it with a scoped, non-destructive script that the user can run themselves from Self Service, or that fires automatically off an identity provider event. No technician touches the machine. No saved credentials are lost.

#The Everyday Problem: Login Keychain Drift

On macOS, the login keychain (~/Library/Keychains/login.keychain-db) is unlocked automatically at login because its keychain password is set to match the account’s login password. When a password changes anywhere other than the standard local login screen – through Platform SSO

without full sync, a forced Okta or Entra ID reset, a password sync agent lag, or a local account password reset performed by IT – the login keychain password falls out of sync with the account password. macOS keeps trying to unlock it automatically using the new password, fails silently, and then prompts the user interactively every time an app touches a keychain item. This generates a very specific, very recognisable ticket pattern: “keeps asking me for my keychain password and nothing works.”

This is distinct from a corrupted keychain database (rare) and from an expired certificate prompt (a different signature entirely). Drift is a sync problem, not a corruption problem, and it has a clean, scriptable fix that does not require deleting anything.

macOS login keychain sync automation

#Prerequisites and Permissions

  • MDM platform with custom Extension Attribute and Self Service (or equivalent) support – examples below use Jamf Pro syntax but the logic ports to any MDM with a scripting and policy layer.
  • Local script execution occurs as the logged-in user, not root, because security keychain operations must run in the user’s security context.
  • No storage of plaintext passwords anywhere – scripts must pipe credentials directly into the security binary and never write them to disk, logs, or MDM inventory fields.
  • Read-only API scope on the identity provider (Okta System Log or Entra ID Audit Log) if you are wiring up the proactive webhook trigger in Step 5.
  • Jamf Pro API role with Send Computer Remote Command and Custom Trigger permissions only – do not grant full API admin to the webhook listener service account.
  • Test scope: a single Smart Group of 10–20 pilot machines before fleet-wide deployment. Blast radius if the detection logic misfires is a false-positive notification, not data loss, because the remediation script never deletes keychain data.

#Implementation Steps

  1. Step 1 – Deploy a lightweight drift detector as a LaunchAgent. Action: push a script and LaunchAgent plist that checks whether the login keychain is unlocked without a password challenge at login. Expected result: a flag file is written only when the keychain fails to auto-unlock. Evidence to capture: presence and timestamp of /Users/Shared/.kc_drift_flag on affected machines.
    1#!/bin/bash
    2# /usr/local/kby/keychain-drift-check.sh
    3# Runs as the logged-in user via LaunchAgent, not root.
    4
    5FLAG="/Users/Shared/.kc_drift_flag"
    6LOGIN_KC="$HOME/Library/Keychains/login.keychain-db"
    7
    8# Attempt a benign read against the login keychain.
    9# A drifted keychain returns error -25293 (SecKeychainItemCopyContent / auth failed)
    10# when the OS tries to silently unlock it.
    11OUTPUT=$(/usr/bin/security find-generic-password -a "$USER" -s "com.apple.safari.https" "$LOGIN_KC" 2u003eu00261)
    12
    13if echo "$OUTPUT" | /usr/bin/grep -q "25293"; then
    14  /usr/bin/touch "$FLAG"
    15  echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) drift-detected" u003eu003e "$FLAG"
    16else
    17  /bin/rm -f "$FLAG"
    18fi
  2. Step 2 – Register the LaunchAgent so the check runs every login and every four hours. Action: deploy the plist via MDM configuration profile or a package postinstall script. Expected result: launchctl list shows the agent loaded for the console user. Evidence to capture: launchctl print gui/$(id -u)/com.kby.keychaindriftcheck output showing state running or last exit code 0.
    1u003c?xml version="1.0" encoding="UTF-8"?u003e
    2u003c!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"u003e
    3u003cplist version="1.0"u003e
    4u003cdictu003e
    5  u003ckeyu003eLabelu003c/keyu003e
    6  u003cstringu003ecom.kby.keychaindriftchecku003c/stringu003e
    7  u003ckeyu003eProgramArgumentsu003c/keyu003e
    8  u003carrayu003e
    9    u003cstringu003e/usr/local/kby/keychain-drift-check.shu003c/stringu003e
    10  u003c/arrayu003e
    11  u003ckeyu003eRunAtLoadu003c/keyu003e
    12  u003ctrue/u003e
    13  u003ckeyu003eStartIntervalu003c/keyu003e
    14  u003cintegeru003e14400u003c/integeru003e
    15u003c/dictu003e
    16u003c/plistu003e
  3. Step 3 – Surface the flag as a Jamf Pro Extension Attribute and Smart Group. Action: create an EA script read at inventory update that reports true or false based on flag presence. Expected result: computers with drift populate a Smart Group named Keychain-Drift-Suspected within one inventory cycle. Evidence to capture: Smart Group membership count and the EA value shown against the device record.
    1#!/bin/bash
    2FLAG="/Users/Shared/.kc_drift_flag"
    3if [ -f "$FLAG" ]; then
    4  echo "u003cresultu003etrueu003c/resultu003e"
    5else
    6  echo "u003cresultu003efalseu003c/resultu003e"
    7fi
  4. Step 4 – Build the non-destructive remediation Self Service policy. Action: publish a Self Service item scoped to Keychain-Drift-Suspected that prompts the user once for their current account password, uses it to resynchronise the keychain password in place, then discards the value immediately. Expected result: subsequent app launches no longer trigger keychain dialogs. Evidence to capture: exit code 0 in the policy log and the drift flag cleared on next detector run.
    1#!/bin/bash
    2# Runs in the user context via Self Service.
    3
    4CURRENT_PW=$(/usr/bin/osascript -e 'Tell application "System Events" to display dialog "Enter your current account password to resync your keychain:" default answer "" with hidden answer' -e 'text returned of result' 2u003e/dev/null)
    5
    6if [ -z "$CURRENT_PW" ]; then
    7  echo "User cancelled remediation."
    8  exit 1
    9fi
    10
    11# Resync the login keychain password to match the current account password.
    12# -o = old password, -p = new password. Piped directly, never written to disk.
    13/usr/bin/security set-keychain-password -o "$CURRENT_PW" -p "$CURRENT_PW" "$HOME/Library/Keychains/login.keychain-db"
    14RESULT=$?
    15
    16unset CURRENT_PW
    17
    18if [ $RESULT -eq 0 ]; then
    19  /bin/rm -f "/Users/Shared/.kc_drift_flag"
    20  /usr/bin/osascript -e 'display notification "Keychain sync repaired. No restart needed." with title "IT Self Service"'
    21  exit 0
    22else
    23  /usr/bin/osascript -e 'display notification "Password did not match. Contact IT if prompts continue." with title "IT Self Service"'
    24  exit 1
    25fi

    Expected output in the Jamf policy log: Script exit code: 0 and a cleared drift flag confirmed by the next scheduled inventory submission.

  5. Step 5 – Wire the proactive trigger from the identity provider. Action: subscribe to the password-change event in your IdP’s system log and fire a lightweight webhook the moment a rotation happens, so the user is nudged to run the Self Service item before drift even causes a prompt. Expected result: the notification lands within seconds of the password change event. Evidence to capture: webhook delivery log and the timestamp gap between IdP event and notification push.
    1{
    2  "eventType": "user.account.update_password",
    3  "actor": {
    4    "id": "00u1a2b3c4d5e6f7g8h9",
    5    "type": "User",
    6    "alternateId": "jane.doe@kby.example"
    7  },
    8  "published": "2024-11-02T09:14:32.101Z",
    9  "outcome": {
    10    "result": "SUCCESS"
    11  },
    12  "target": [
    13    {
    14      "id": "00u1a2b3c4d5e6f7g8h9",
    15      "type": "User",
    16      "alternateId": "jane.doe@kby.example"
    17    }
    18  ]
    19}
    1from flask import Flask, request, abort
    2import hmac, hashlib, os, requests
    3
    4app = Flask(__name__)
    5WEBHOOK_SECRET = os.environ["OKTA_WEBHOOK_SECRET"]
    6JAMF_BASE = os.environ["JAMF_BASE_URL"]
    7JAMF_TOKEN = os.environ["JAMF_API_TOKEN"]
    8
    9@app.route("/okta/password-change", methods=["POST"])
    10def handle_event():
    11    signature = request.headers.get("X-Okta-Verification-Signature", "")
    12    computed = hmac.new(WEBHOOK_SECRET.encode(), request.data, hashlib.sha256).hexdigest()
    13    if not hmac.compare_digest(signature, computed):
    14        abort(401)
    15
    16    payload = request.get_json()
    17    event_type = payload.get("eventType", "")
    18    if event_type != "user.account.update_password":
    19        return "", 204
    20
    21    user_email = payload["actor"]["alternateId"]
    22
    23    # Trigger a Jamf Pro custom trigger that pushes a Self Service reminder
    24    # notification to the matching device record. No password data is handled here.
    25    requests.post(
    26        f"{JAMF_BASE}/JSSResource/computercommands/command/CustomEvent",
    27        headers={"Authorization": f"Bearer {JAMF_TOKEN}"},
    28        json={"trigger": "prompt-keychain-resync", "user": user_email},
    29        timeout=10
    30    )
    31    return "", 200

#Verification and Expected Evidence

Confirm success across three layers. First, the detector: run /usr/local/kby/keychain-drift-check.sh manually and confirm no flag file is created on a healthy machine. Second, the Smart Group: after remediation, the device drops out of Keychain-Drift-Suspected within one inventory cycle – capture a before-and-after screenshot of group membership count. Third, the user experience: ask the pilot user to open Mail or Safari and confirm zero keychain prompts across a full working day. Log this as a closed-loop verification note on the change record, not a ticket.

#Rollback

The remediation script is non-destructive by design – it resynchronises the existing keychain password rather than deleting the keychain, so there is no data loss to roll back. If the script fails validation during pilot (exit code 1, or the drift flag persists after a successful-looking run), disable the Self Service policy scope back to the pilot Smart Group, unload the LaunchAgent via a removal script pushed through MDM, and revert affected users to the manual System Settings walkthrough until the script logic is fixed. Keep the webhook listener paused (return 503 to the IdP) rather than deleting the endpoint, so the IdP does not silently drop the subscription.

Stopping Login Keychain Prompt Loops Before They Escalate architecture diagram 2

#Failure and Escalation Conditions

Escalate to a human technician when any of the following occur: the remediation script returns exit code 1 after the user has confirmed the password entered was correct, which indicates the underlying keychain file may be corrupted rather than merely desynchronised, and needs the destructive rebuild path with proper data-loss consent. Escalate when a single device triggers the drift flag more than three times in 24 hours, since repeated drift after a successful resync usually points to a Platform SSO configuration fault or a password sync agent looping. Escalate when the webhook listener logs signature verification failures, which may indicate a compromised or misconfigured IdP integration and should be treated as a security event, not a helpdesk item. The monitoring signal to watch on the automation side is the ratio of Self Service remediation runs to Smart Group entries per day; a sustained spike suggests an upstream password policy change is causing mass drift and warrants a broader fix rather than per-device remediation.

#Measuring Ticket Deflection

Baseline the current volume by pulling the last 90 days of tickets tagged with keywords like “keychain”, “always asking for password”, and “wants to use login keychain” from your ticketing system. After deployment, track three numbers monthly: Smart Group entries into Keychain-Drift-Suspected, Self Service remediation runs with exit code 0, and residual tickets on the same keyword filter. A successful rollout typically shows Smart Group entries holding steady or rising slightly (better detection) while ticket volume on the same keyword set drops close to zero within one full password rotation cycle, since that is when the old-way tickets used to spike. Report deflected tickets as detections resolved by Self Service divided by total detections, and keep the webhook-to-notification latency as a secondary KPI to prove the proactive path is catching drift before users notice it.

Evidence trail

Sources and verification

Primary documentation and external technical references used in this article.

  1. 01security command-line tool documentationss64.com
  2. 02Keychain Access behaviour on macOSsupport.apple.com
  3. 03Okta’s System Log API referencedeveloper.okta.com
  4. 04Jamf Pro API developer documentationdeveloper.jamf.com
Emi Nakamura

Emi Nakamura

Ops Playbook Architect

Emi Nakamura is a Platform Engineer specialising in developer experience and continuous delivery systems.

Published
View Profile
Reader Interaction

Comments

Add a thoughtful note on Stopping Login Keychain Prompt Loops Before They Escalate. Comments are checked for spam and held for moderation before appearing.

Loading comments...
Comment submission is disabled until Cloudflare Turnstile keys are configured.

Learn More About KBY

Was this useful?

Operate smarter, with fewer recurring tickets.

Receive new operational playbooks, incident-prevention guidance, automation scripts and recovery runbooks.