CLAUDE LABJP
RELEASE — Claude Code v2.1.243 shipped on August 25 with broad improvements across usage reporting, model selection, sign-in, and reliabilityUSAGE — /usage now breaks results down per loop, showing run count, total tokens, tokens per run, and last run, which makes a chatty /loop task easy to spotSETTINGS — modelPicker lets you curate the /model list with your own order and labels, while promptCacheTtl and subagentPromptCacheTtl let the main conversation and subagents keep different cache lifetimesLOGIN — /login now offers keyless sign-in with an Anthropic Console account, so organizations that do not permit API keys can still get inPERFORMANCE — The native binary is now zstd-compressed, dropping from roughly 340MB to 75MB on Linux x64, and each session uses about 40 to 60MB less memoryFIX — v2.1.245 resolves a startup crash on distributions shipping glibc 2.44, including Arch Linux, CachyOS, and Fedora RawhideRELEASE — Claude Code v2.1.243 shipped on August 25 with broad improvements across usage reporting, model selection, sign-in, and reliabilityUSAGE — /usage now breaks results down per loop, showing run count, total tokens, tokens per run, and last run, which makes a chatty /loop task easy to spotSETTINGS — modelPicker lets you curate the /model list with your own order and labels, while promptCacheTtl and subagentPromptCacheTtl let the main conversation and subagents keep different cache lifetimesLOGIN — /login now offers keyless sign-in with an Anthropic Console account, so organizations that do not permit API keys can still get inPERFORMANCE — The native binary is now zstd-compressed, dropping from roughly 340MB to 75MB on Linux x64, and each session uses about 40 to 60MB less memoryFIX — v2.1.245 resolves a startup crash on distributions shipping glibc 2.44, including Arch Linux, CachyOS, and Fedora Rawhide
Articles/Cowork
Cowork/2026-07-12Advanced

The Night a Connector Grew Write Tools, Your Unattended Job Could Quietly Send Something

The moment a connector update quietly adds write tools, a job you run unattended can suddenly send email or delete files. Here is how to build a gate that snapshots a connector's tool surface, diffs it, and stops unapproved writes before they run — drawn from indie development.

cowork14mcp20connector2unattended3security17

Premium Article

I was reviewing the logs of a nightly job the next morning, as usual. The run itself had succeeded. But the tool list of the connector I use held a few names that were not there the week before: create_message, send_message, delete_file. The upstream service had updated, and a connector that used to only read could now compose and send email, and manipulate files.

That progress is welcome in itself. And yet a small chill ran down my back. My nightly job picks the tools a connector offers on its own, according to the situation. Until yesterday, the worst outcome of a bad decision was a misread. From tonight, the same misjudgment could turn into a send, or a delete. The danger was not born the moment my code changed. It was born the moment the connector's tool surface quietly widened.

In July 2026 the Microsoft 365 connector gained write tools — composing, sending, and organizing email, calendar operations, and creating or updating files on OneDrive and SharePoint. A human sitting beside the machine can pause a breath before the Send button. In unattended operation, unless you build that breath into the design yourself, no one stops you. Drawing on running four sites unattended as an indie developer, we build, as working code, a gate that detects when tools have grown and dams up unapproved writes before they execute.

The danger is born when a new tool appears

When we think about the security of an unattended job, we tend to look at our own prompt and permission settings. But the range of operations an agent can actually call is, in the end, decided by the tool list the connector exposes. That was my blind spot. Without changing a single character of my code or my permission scope, the upstream service bumping a version can widen the set of possible operations.

With a read-only connector, the worst failure stops at "read the wrong thing." Add one write tool and the nature of failure changes. A misread can be rolled back; a sent email or a deleted file does not come back easily. So what deserves watching is not "what tools exist now" but the diff: "what grew since last week."

I settled on a policy. Detect the change in tools every time — above all, the appearance of a new write or destructive tool. Do not let an unapproved one proceed to execution. Approval exists only when I explicitly write it into a policy file. This direction — a newly grown write tool is denied by default — sits at the very outer edge of the unattended job.

Sort tools into read / write / destructive automatically

The first part is a classifier that sorts the tool list by risk. MCP tools carry a name and a description. Perfect semantic analysis is not needed. Sorting into three tiers from the verb in the name is enough in practice. Anything ambiguous is pushed to the safe side and collected as "needs review."

# classify_tools.py
import re
from enum import IntEnum
 
class Risk(IntEnum):
    READ = 0         # reference only; nothing to roll back
    WRITE = 1        # changes state; usually reversible
    DESTRUCTIVE = 2  # send / delete / payment; hard to reverse
 
# verbs treated as destructive (a send, a delete)
DESTRUCTIVE = r"(send|delete|remove|purge|archive|pay|charge|transfer|deploy|publish|revoke)"
# common write verbs
WRITE = r"(create|update|write|edit|insert|upsert|move|rename|set|add|append|patch|put|post)"
# read verbs
READ = r"(get|list|search|read|fetch|find|query|describe|lookup|view)"
 
def classify(tool_name: str) -> Risk:
    name = tool_name.lower()
    verb = re.split(r"[_\-.]", name)[0]  # treat the leading token as the verb
    if re.fullmatch(DESTRUCTIVE, verb):
        return Risk.DESTRUCTIVE
    if re.fullmatch(WRITE, verb):
        return Risk.WRITE
    if re.fullmatch(READ, verb):
        return Risk.READ
    # if the verb is unknown, scan the whole name for a destructive word
    if re.search(DESTRUCTIVE, name):
        return Risk.DESTRUCTIVE
    if re.search(WRITE, name):
        return Risk.WRITE
    # still unknown: do not assume READ; pick it up as WRITE
    return Risk.WRITE

I include pay and charge among the destructive verbs because I have payment connectors in mind. If a billing connector like Stripe starts offering create_charge or refund, an unattended job could actually move money — exactly the kind of irreversible operation I want stopped by default to avoid an accident.

The line I cared about is the last one. Treating an unknown verb optimistically as READ would turn the classifier itself into a hole. Leaning "unknown means write or worse" pushes the errors onto the side of over-detection. Over-detection I can clear with a single approval; a destructive tool I miss goes unnoticed until morning. You decide, in advance, which error you are willing to carry.

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
A way to snapshot the tools a connector exposes and, by diffing against last week, surface only the write tools that newly appeared
A classifier that sorts tool names into read / write / destructive by verb, plus a preflight gate that halts unapproved destructive tools before they run
Measured results from a two-stage guard that logs every intent to send or delete into a dry-run ledger for a next-morning review
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 $15 for lifetime access
View Membership →

Related Articles

Cowork2026-08-23
Not Every Unreachable Connector Means the Same Thing in an Unattended Run
When an unattended task cannot reach a connector, the cause splits into three states: still connecting, unauthenticated, or genuinely absent. Each demands the opposite response. Here is the resolver that tells them apart.
Cowork2026-07-17
It Worked on My Machine, but Nobody Could Trigger It — Four Assumptions I Stripped Out of a Cowork Plugin
I bundled four working automation skills into a plugin and shared it. Only one of them ever fired. Here is how I measured skill trigger rate, and the four assumptions — vocabulary, paths, connections, and naming — I had to strip out before it was portable.
Cowork2026-07-05
When Claude Declines a Request on Safety Grounds, What Should an Unattended Pipeline Return?
A third kind of ending that is neither an error nor a normal completion — a safety decline. Here is how to fold it into a pipeline you run unattended, with a classifier and a review-queue design drawn from indie development.
📚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 →