Playbook

My 6AM Agent Couldn't Log In to 17 of Its 30 Tools

August 1, 2026 · By Italo Campilii

My 6AM Agent Couldn't Log In to 17 of Its 30 Tools

TL;DR

My daily 6AM agent starts with no human attached. This morning it reported 17 of the 30 MCP servers it could see as requiring authorization before their tools could be used — and it is explicitly told that a non-interactive session cannot run the sign-in flow. So those seventeen capabilities were configured, healthy, and unreachable. The split was not random: every server carrying its own static credential came up; the ones behind an interactive sign-in were the ones that didn't. Interactive OAuth is the only dependency in an agent fleet that cannot self-heal, because repairing it requires the one thing an unattended run doesn't have. If a scheduled agent needs a tool, that tool needs machine credentials — not a session someone logged into once.

Two days ago I wrote about an agent that failed safely for nine days and told nobody. Yesterday I measured how much unused capability my agents carry. This morning the same 6AM job handed me the third variation on the theme, and it's the one I like least: capability that is installed, current, and simply out of reach.

The run started at 06:00:00. By 06:00:32 it had already written down which of its tools it couldn't open.

What the run actually reported

Claude Code keeps a small file recording the servers that came back needing authorization. Here it is from this morning, unedited:

$ ls -la ~/.claude/mcp-needs-auth-cache.json
-rw-r--r--  1 italo  staff  389 Aug  1 06:00 mcp-needs-auth-cache.json

$ python3 -c "import json,datetime,pathlib; \
d=json.loads(pathlib.Path.home().joinpath('.claude/mcp-needs-auth-cache.json').read_text()); \
[print(datetime.datetime.fromtimestamp(v['timestamp']/1000), '|', k) \
 for k,v in sorted(d.items(), key=lambda x: x[1]['timestamp'])]"

2026-08-01 06:00:28.112 | claude.ai Smithery
2026-08-01 06:00:28.113 | claude.ai n8n
2026-08-01 06:00:28.136 | claude.ai Cloudflare Developer Platform
2026-08-01 06:00:28.137 | claude.ai Similarweb
2026-08-01 06:00:28.144 | claude.ai Cloudinary
2026-08-01 06:00:28.263 | claude.ai Semrush
2026-08-01 06:00:31.399 | higgsfield
2026-08-01 06:00:32.141 | n8n

Eight servers, named on disk, inside the first thirty-two seconds. The session's own startup report was broader than the cache: 17 servers listed as requiring authorization, 12 more still connecting (all twelve resolved), plus one stdio server whose tools were live from the start. Thirty servers in view, seventeen of them shut.

And this is the part that matters more than the count. The run is told, in plain language, that a non-interactive session cannot perform the sign-in flow, and that it must not ask anyone for a code or a callback URL. That instruction is correct. It is also a closed door. An agent with no terminal in front of it cannot click "Authorize," and there is no amount of retry logic that changes that.

6AM RUN, 2026-08-01 — 30 MCP SERVERS IN VIEW ALL 30 17 blocked on auth 12 connected late 1 BY AUTH STYLE OAUTH ×23 14 needed re-auth 9 fine LOCAL ×7 3 4 fine STATIC ×2 2 fine 0 failures — env credentials and static headers TAKEAWAY Nothing carrying its own credential failed. Everything that needed a human session was a coin flip the run was not present to call. Counts from the 06:00 run's startup report and ~/.claude/mcp-needs-auth-cache.json. Static-credential sample is small (n=2).

The split is by credential style, not by vendor

I went into my config expecting the failures to cluster by who runs the server, or by transport. They didn't. Here's what ~/.claude.json holds for my globally configured servers:

ServerTransportHow it authenticates6AM result
amazon-sp-apistdioCredentials in envLive at startup
signumhttpStatic headersConnected
trader-devsseBare URL, remote sessionConnected
n8nhttpBare URL, remote sessionNeeded auth
higgsfieldhttpBare URL, remote sessionNeeded auth

Both servers that carry their own credentials in the config came up. Two of the three that rely on a remote session did not. That is a tiny sample and I won't pretend otherwise — but the account-level connectors give the same answer with a larger n: of 23 interactive-OAuth connectors, 14 needed re-authorization and 9 were fine. Roughly three in five, on a morning when nothing was wrong and nothing had changed.

Transport didn't predict anything. http appears on both sides of the table. Vendor didn't predict anything either. The only variable that tracked the outcome was whether the credential lives somewhere a process can read at 6AM, or in a session a person established at some point and hasn't refreshed since.

This is the same shape as the launchd PATH bug I wrote up in July — something that is unambiguously present in the interactive environment and unambiguously absent in the scheduled one. I keep rediscovering that the interactive shell is a generous liar. Everything works there.

Why this failure mode is worse than a crash

A crash is loud and a crash is honest. This isn't either.

When an MCP server is unreachable, the agent doesn't stop. It has a task, it has instructions, and it has thirteen other servers that answered. So it does the work with what it has — and if the missing capability was, say, a rank-tracking API or an image generator, the run completes and reports success with a hole in the middle of it. That is the verification gap restated as a dependency problem: the run's exit code describes the run, not the result.

Three properties make it particularly awkward:

It is invisible from the outside. The job succeeded. The log looks normal. Unless you specifically emit "server X was unavailable," nothing downstream ever learns that a capability was missing.

It cannot self-heal. Every other failure in a fleet has a retry story. A network blip retries. A rate limit backs off. A bad path gets fixed by an absolute path. Expired interactive auth has no retry that works, because the repair action is a human opening a browser. Retrying it is a loop that can only burn tokens.

It degrades silently over time. Tokens expire on their own schedule, one at a time. There's no moment where the fleet breaks; there's a slow drift where each week the 6AM run can reach slightly less than it could the week before. Fourteen of twenty-three is not an event. It's an accumulation, and accumulation is exactly the class of problem I've now written about three days running.

The spec is moving toward this, not away from it

The MCP specification revision 2026-07-28 shipped four days ago, and it tightens authorization considerably: MCP servers are now formally OAuth 2.1 resource servers, with RFC 9728 protected-resource metadata for authorization-server discovery, RFC 8707 resource indicators so a token names the server it's meant for, issuer verification to block mix-up attacks across multi-server setups, and a required OpenID Connect application_type declaration at client registration (WorkOS, 2026).

All of that is good, and none of it is aimed at my problem. It makes interactive auth safer; it does not make it available at 6AM. The headless answer sits in a different part of the same standard: machine-to-machine credentials via the client-credentials grant, where a service account requests a token directly with no browser in the loop. Those access tokens are short-lived — on the order of three hours — and that grant issues no refresh token, so the agent's job is to request a fresh one rather than to keep one alive (MCP OAuth 2.1 guide, 2026). Practitioner guidance is to renew with a small overlap before expiry so concurrent tool calls don't race a rotating token.

The ecosystem is arriving at the same conclusion from several directions at once. AWS added non-interactive OAuth to its MCP server in July 2026 specifically so agents already holding cloud credentials can mint tokens without a person. Atlassian added API-token authentication to its Rovo MCP server. And the collision itself has a name in the research now — agents that authenticate fine in a developer terminal and then fail under a process supervisor after a reboot, because "log in once" was never a deployment strategy (Zylos Research, 2026).

What I'm changing

Four rules, and the first one is the only one that requires thought.

1. Classify every server as fleet-critical or interactive-only. A capability my scheduled runs depend on and a capability I reach for while sitting at the machine are different objects with different requirements, and I had been keeping them in one undifferentiated list. Cloudflare and my SEO data sources belong to the fleet. A travel-booking connector does not. Only the first group has to survive 6AM; the second can stay behind a session and re-authorize whenever I next open it. This cuts the actual problem down from seventeen servers to a handful.

2. Fleet-critical servers get machine credentials, not sessions. For that handful, move off interactive OAuth: client-credentials service accounts, API tokens, or credentials in env/headers read from a secrets store at launch. The two servers in my config that already work this way have never once needed attention. Store the secret in the keychain or a secrets manager rather than pasting it into a config file — the credential is now long-lived and unattended, which raises its value to anyone who finds it.

3. Make missing capability loud. This is the safe-rollout discipline applied one layer down: a run that couldn't reach a fleet-critical server should say so in its own log and fail its checks, rather than quietly producing a thinner result. The mcp-needs-auth-cache.json file already exists on disk with timestamps in it. Reading it at the end of a run and diffing it against the fleet-critical list is a few lines, and it converts an invisible degradation into a line I'll actually see.

4. Re-authorize on a calendar, not on discovery. Until every fleet-critical server has machine credentials, the interactive ones need a standing appointment — the same monthly pass where I audit scheduled jobs and prune skills. Finding out that a connector expired because a run needed it means the run already went out wrong.

None of this is exotic. It's the ordinary discipline of separating "works when I'm here" from "works when I'm not," which is the entire difference between automation and a fleet. I have written that sentence in three different costumes this week: a job that exited cleanly and told nobody, a library that grew because nothing asked it to shrink, and now a set of credentials that were valid the last time a human looked.

The pattern underneath all three is that unattended systems only inherit the parts of your setup that don't need you. Everything else quietly stays behind.

FAQ

Why do MCP servers need re-authentication in headless or scheduled runs?

Because most remote MCP servers authenticate through an interactive OAuth flow, and that flow's tokens expire on a schedule of the provider's choosing. Renewing them requires a browser, a redirect, and a person to approve the grant. A scheduled agent has none of those, and a well-built client will explicitly refuse to ask the user for a code or callback URL in a non-interactive session. So the server isn't broken and the config isn't wrong — the repair action simply isn't available at 6AM.

How do I tell which MCP servers my unattended agent can actually reach?

On Claude Code, read ~/.claude/mcp-needs-auth-cache.json after a run. It names the servers that came back needing authorization, with millisecond timestamps, and its mtime tells you which run wrote it. Compare that against your configured servers in ~/.claude.json — the mcpServers object at the top level for global ones, plus any project-scoped entries under projects. The gap between those two lists is the capability your scheduled runs think they have and don't.

What is the fix for MCP OAuth in a scheduled agent?

Move fleet-critical servers to machine credentials. That means the OAuth client-credentials grant with a service account where the provider supports it, an API token where one is offered, or credentials supplied directly in the server's env or headers config read from a secrets store at launch. Client-credentials access tokens are short-lived — roughly three hours — and that grant issues no refresh token, so the agent requests a new one rather than maintaining a long-lived session. Renew slightly ahead of expiry so parallel tool calls don't race the rotation.

Isn't storing a long-lived credential less safe than an OAuth session?

It's a different trade, and it needs to be handled as one. An unattended credential is by definition available to any process that can read it, so it belongs in the OS keychain or a secrets manager rather than a plaintext config or a shell profile, and it should be scoped to exactly what the agent's job requires. What you gain is a dependency that behaves the same at 6AM as it does at 6PM. What you must not do is grant an unattended agent broader access than the interactive session it replaced, on the grounds that it's more convenient.

Did the 2026-07-28 MCP spec update solve this?

No, and it wasn't trying to. The 2026-07-28 revision makes MCP servers formal OAuth 2.1 resource servers and hardens the interactive path — protected-resource metadata for discovery, resource indicators so a token names its intended server, issuer verification against mix-up attacks, and a declared client application type. All of that makes authorization safer and better specified. None of it puts a human in front of a browser at six in the morning. The headless answer is the machine-to-machine side of the same standard, which is why providers have been shipping non-interactive token paths for their MCP servers through mid-2026.

RUNNING AGENTS UNATTENDED?

The hard part of a fleet isn't the model — it's everything that has to hold up while nobody is watching: credentials, scheduling, verification, and the reporting that tells you when a run went out thinner than it should have. That's the work I do. Get in touch, or see the press and hire page.

— Italo Campilii. Measured 2026-08-01 from that morning's own unattended run; the commands above are reproducible on any Claude Code install.

IC

Italo Campilii

AI systems builder and one-person operator. I run the infrastructure for five brands solo with Claude Code agent fleets — SEO, content, e-commerce ops, and the verification layer that keeps it honest. The Build Log is where I write down what actually works.