◉CLAUDE LABJP
●2.1.302 — Fork subagents are now on by default in claude -p, the Agent SDK and the desktop app. Turn them off with CLAUDE_CODE_FORK_SUBAGENT=0 (10/16)●2.1.307 — Fixes MCP failing to reconnect after sleep or a keychain lock (10/21). 2.1.306 also fixed the false claude -p login message●SONNET 4.5 — Retires on the Claude API on 11/30, 38 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…●2.1.302 — Fork subagents are now on by default in claude -p, the Agent SDK and the desktop app. Turn them off with CLAUDE_CODE_FORK_SUBAGENT=0 (10/16)●2.1.307 — Fixes MCP failing to reconnect after sleep or a keychain lock (10/21). 2.1.306 also fixed the false claude -p login message●SONNET 4.5 — Retires on the Claude API on 11/30, 38 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…
Articles/Claude Code
⟐ Claude Code/2026-10-23Intermediate

When a Nightly claude -p Said MCP Needed Authentication, I Added a Preflight That Splits Sleep From Keychain

Claude Code 2.1.307 fixed MCP failures caused by macOS sleep and a locked login keychain. Here is a preflight script that helps with or without the fix, and a way to match failures against sleep history.

Claude Code273MCP59claude -p4macOSOperations23

✦ Premium Article

If you run claude -p overnight on a Mac at home, you may recognize a certain kind of morning. You open the log and an MCP server says "needs authentication", or it is treated as timed out and stays failed to the end. You changed nothing the night before.

The Claude Code 2.1.307 changelog has two fixes that match this situation.

SymptomWhat 2.1.307 says changed
On macOS, MCP shows "needs authentication" in `claude -p` while the login keychain is lockedIt now says why, and the next run after an unlock connects without a new sign-in
MCP servers reported as timed out, and staying failed in headless runs, when macOS or Linux went to sleep while they were connectingFixed

If it is fixed, you could simply update. I decided differently. Even after the fix, keeping a check for "can MCP be used right now?" before a nightly run keeps the morning loss small. This is the preflight I use, and the steps I follow to find the cause when a run still fails.

Split the failure into three

Failures that look alike in the morning have different causes.

  • Sleep: the Mac went to sleep mid-connection, and the failed state outlived the wake
  • Keychain: the login keychain was locked, so stored credentials could not be read
  • Expired authentication: the token was revoked on the server side; waiting does not help

The first two tend to heal by waiting or by fixing the environment. The third needs a person to sign in again. The point of checking before the run is to tell them apart in advance: wait once for a failure that can heal, and skip at once for one that cannot.

The preflight: turn claude mcp list into one JSON line

#!/usr/bin/env python3
"""Read the output of claude mcp list and return one line describing MCP health before a nightly run.
Exit code: 0 = all connected / 3 = needs auth, failed or unreadable / 4 = claude cannot run
"""
import re, subprocess, sys, json
 
STATE_PATTERNS = [
    ("connected", re.compile(r"Connected", re.I)),
    ("needs_auth", re.compile(r"Needs authentication", re.I)),
    ("failed", re.compile(r"Failed to connect|timed out|timeout", re.I)),
]
 
def classify(line: str) -> str:
    for name, pat in STATE_PATTERNS:
        if pat.search(line):
            return name
    return "unknown"
 
def parse(text: str) -> dict:
    servers = {}
    for raw in text.splitlines():
        line = raw.strip()
        # e.g. "github: npx -y @x/server - ✓ Connected"; the server name ends at the first ": "
        m = re.match(r"^([\w.\-]+):\s+(.*)$", line)
        if not m:
            continue
        servers[m.group(1)] = classify(m.group(2))
    return servers
 
def main() -> int:
    try:
        out = subprocess.run(["claude", "mcp", "list"], capture_output=True,
                             text=True, timeout=90).stdout
    except (FileNotFoundError, subprocess.TimeoutExpired) as e:
        print(json.dumps({"preflight": "no_claude", "error": type(e).__name__}))
        return 4
    servers = parse(out)
    if not servers:  # zero servers is not "all connected"; it means "could not read"
        print(json.dumps({"preflight": "empty_list", "raw_head": out[:120]}, ensure_ascii=False))
        return 3
    bad = {k: v for k, v in servers.items() if v != "connected"}
    print(json.dumps({"preflight": "ok" if not bad else "degraded",
                      "total": len(servers), "bad": bad}, ensure_ascii=False))
    return 0 if not bad else 3
 
if __name__ == "__main__":
    sys.exit(main())

In the output I could check, claude mcp list printed lines shaped like "server name: command - status". Symbols and wording may differ by version, so the regular expressions match only the words for states. Run claude mcp list once on your own machine and confirm the line shape first.

A blind spot I hit while writing it

My first version returned ok whenever bad was empty. Then I ran it where claude mcp list printed nothing. With zero servers it reported ok and exited 0.

"No problem servers" and "no servers could be read" are different states. A preflight that returns ok on zero entries waves things through exactly when the command itself is not behaving as expected. The empty_list branch above is the fix. If you use no MCP servers at all, change it to treat zero as normal.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦You will be able to add a one-line preflight to your own machine that decides whether MCP is usable before a nightly claude -p run, and skips the run when it is not
✦You will be able to tell, from pmset history, whether a morning MCP failure came from sleep or from authentication
✦You will be able to avoid the 'zero servers, so ok' blind spot before you write the script
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

⟐ Claude Code2026-10-23
When a Nightly claude -p Says MCP "Needs Authentication": Check Mac Sleep and the Keychain First
If an unattended claude -p run leaves "needs authentication" or MCP timeouts in the morning log, check whether the Mac slept or the login keychain was locked. What v2.1.307 changed, and what it still leaves to you.
⟐ Claude Code2026-07-29
The MCP Server Connects Fine and Still Hands Back Zero Tools — Catching Silent Capability Drift with a Tool Manifest Diff
A missing credential does not break the MCP handshake. initialize succeeds, tools/list succeeds, and the array comes back empty. Here is the measured behaviour, and a preflight that locks the expected tool surface and fails closed before the agent ever starts.
⟐ Claude Code2026-10-22
When Only claude -p Refuses to Log In: Check the Credential Order Before You Re-Login
Interactive Claude Code works but claude -p or a script is refused? Here is where to look before running /login again, following the credential order in the official docs.