CLAUDE LABJP
CODE — Claude Code ships a broad quality and reliability update with /rewind, stronger MCP resilience, and steadier OAuth handlingCODE — CPU and memory use drops during streaming and long sessions, keeping always-on automation stableADMIN — New org model restrictions let administrators control which models are availableMCP — Structured output, remote MCP, and session resume all get more reliableMODEL — Claude Fable 5 is generally available, with a 1M-token context window, always-on adaptive thinking, and 128K outputLINEUP — Opus 4.8, Sonnet 4.6, and Haiku 4.5 lead the lineup; pick the right one per taskCODE — Claude Code ships a broad quality and reliability update with /rewind, stronger MCP resilience, and steadier OAuth handlingCODE — CPU and memory use drops during streaming and long sessions, keeping always-on automation stableADMIN — New org model restrictions let administrators control which models are availableMCP — Structured output, remote MCP, and session resume all get more reliableMODEL — Claude Fable 5 is generally available, with a 1M-token context window, always-on adaptive thinking, and 128K outputLINEUP — Opus 4.8, Sonnet 4.6, and Haiku 4.5 lead the lineup; pick the right one per task
Articles/Claude Code
Claude Code/2026-06-27Advanced

When an OAuth Token Expires, Your Unattended Run Has Nowhere to Go — A Token-Lifecycle Design That Keeps Remote MCP Alive

Remote MCP connectors are authorized via OAuth, but access tokens are short-lived. Interactive sessions can re-authorize in a browser; an unattended scheduled run has nobody to click the dialog. Here is a token-lifecycle design that owns expiry and refreshes ahead of time.

Claude Code169MCP36OAuth3Authentication2Indie Development6

Premium Article

A job that was supposed to run at 2 a.m. has produced nothing by morning. Tracing the log, a single remote MCP tool call failed, and the cause was an expired access token. When you work interactively, an expired token is invisible: an authorization dialog appears, you click a button in the browser once, and everything continues. But an unattended scheduled run has nobody to click that button.

As an indie developer, I run automated posting across several sites for Dolice Labs, and more than once a freshly connected remote MCP connector worked fine, only to stall quietly some tens of hours later. The connector configuration itself was correct. What had broken was the one thing nobody was managing: the token's lifespan.

This article lays out, with working code, how to hold a remote MCP connector's OAuth tokens as state that you own — refreshing them ahead of time, both before and during a run — so your unattended jobs keep moving.

Why token expiry is invisible during interactive use

An OAuth access token is usually short-lived, expiring in anywhere from tens of minutes to a few hours. The refresh token, by contrast, is long-lived, and you can use it to obtain a new access token without a human present.

Interactive clients do this renewal silently behind the scenes. If the access token has expired, they quietly trade the refresh token for a new one; if that has also expired, they open a browser and ask the human, "Authorize?" In other words, the assumption that "a human can always click the authorize button" is exactly what turns expiry into an invisible problem.

That assumption collapses under unattended execution. Opening a browser does nothing because there is no one to click, so the moment both the access token and the refresh token expire, the job can no longer move forward. Worse, expiry comes back as a 401, which the MCP client layer tends to flatten into a generic tool error. All you see is "the tool failed," and it takes a while to realize the real reason was "authorization expired."

So for unattended operation, you cannot leave renewal to the client. You have to hold the lifecycle yourself. Let's build it up step by step.

Hold the token as state with an expiry

The first step is to persist the access token, refresh token, and expiry time together. Without an expiry time you have no idea when it will lapse, which makes refreshing ahead of time impossible.

import json
import os
import time
import tempfile
from dataclasses import dataclass, asdict
from pathlib import Path
 
@dataclass
class TokenSet:
    access_token: str
    refresh_token: str
    expires_at: float  # UNIX seconds: the absolute time the access token expires
 
    def expires_in(self) -> float:
        return self.expires_at - time.time()
 
class TokenStore:
    """Persists the token set as JSON. Writes are atomic."""
 
    def __init__(self, path: str):
        self.path = Path(path)
 
    def load(self) -> TokenSet:
        data = json.loads(self.path.read_text(encoding="utf-8"))
        return TokenSet(**data)
 
    def save(self, tokens: TokenSet) -> None:
        # Write to a temp file in the same directory, then rename.
        # If the process dies mid-write, we never overwrite with a partial file.
        d = self.path.parent
        d.mkdir(parents=True, exist_ok=True)
        fd, tmp = tempfile.mkstemp(dir=d, suffix=".tmp")
        try:
            with os.fdopen(fd, "w", encoding="utf-8") as f:
                json.dump(asdict(tokens), f)
            os.replace(tmp, self.path)
            os.chmod(self.path, 0o600)  # only the owner may read the token file
        finally:
            if os.path.exists(tmp):
                os.unlink(tmp)

The quietly important part is that the save is atomic (temp file plus rename). If the process dies while saving tokens, a half-written file is left behind and the next load fails. Renewal happens on every unattended run, so a break here becomes a breeding ground for silent stalls. The chmod 0o600 narrows the file's permissions because tokens are secrets.

Token endpoints usually return expires_in (seconds remaining) rather than expires_at (an absolute time), so convert it to an absolute time with time.time() + expires_in the instant you receive it and store that. Keeping seconds-remaining forces you to separately remember when you fetched it, which is a source of drift.

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
Owning the access token's expiry yourself and refreshing ahead of time using a skew window
A token store resilient to refresh-token rotation invalidation and clock skew
A retreat design that fails loudly with a structured skip instead of stalling silently
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-04-26
Untangling Claude Code's 'Authorization Failed' Error — OAuth, MCP, and Dynamic Client Registration
Why Claude Code suddenly throws 'authorization failed' or 'incompatible auth server: does not support dynamic client registration', and what to actually do about it. A practical walkthrough of OAuth, MCP server requirements, and the real fixes that work in production.
Claude Code2026-06-21
When Claude Code Still Writes Stale Code With Context7 Installed — Verifying the Injection Actually Worked
You installed Context7 MCP, yet Claude returns code against a deprecated API. Most of the time the documentation injection silently no-ops. Here are field notes on a hook that detects misses, pinning doc versions, and a harness that checks generated code every time.
Claude Code2026-06-20
Keep MCP Connector Authorization in One Place — A Solo Dev Design That Survives Growing Integrations
When Claude chat, Claude Code, and Cowork each configure the same MCP connector separately, their authorization drifts apart and breaks silently. Here is how to borrow the managed-connector idea of 'provision once, reuse everywhere' as an indie developer.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →