Three custom scripts were going to live in my agent toolkit. Two of them already had code. By the end of one week, all three were dead, and the reason was the same every time: I was about to hand-roll something the platform had already solved natively. It was already built and shipped, sitting there in the docs while I opened a new file and started typing an import block.

The thing that killed them isn’t discipline or taste. It’s a five-minute check I keep forgetting to run, and every time I skip it I spend a weekend on a problem that was never mine to solve.

The two that already had code

The first casualty was a cost tracker. I run a fleet of LLM providers behind a router, and I wanted spend awareness baked in, so I wrote cost_tracker.py. Reasonable instinct. The second was latency_tracker.py, an exponential-moving-average tracker that watched per-provider latency and error rates so the router could lean toward whatever was healthy that hour. Also reasonable. Both were real files with real logic. Writing them felt productive because I could watch them work in a test.

Both got deleted in the same review. Not because the logic was wrong, but because they were the wrong altitude. They were solving a routing-and-observability problem that the layer above them was already positioned to handle, and keeping them meant maintaining a private copy of behavior that wanted to live somewhere shared. Every line of a bespoke latency tracker is a line you now own forever: you patch it, you debug it at 1 AM when a provider changes its error envelope, you carry it through every refactor. It is maintenance debt the instant it merges, and the only thing that justifies that debt is if nothing else can do the job. Here, something else already did.

The third one never got written

The third was going to be digest_job.py. The pitch was clean: every night, walk the repos, synthesize what moved, and leave me a digest to read with coffee. Overnight multi-repo synthesis. I had the shape of it in my head and I was a tab away from creating the file.

Then I actually looked at what the platform shipped, and Claude Code Routines covered the exact need. Cloud-hosted scheduled tasks running on the vendor’s infrastructure. The “run a synthesis job overnight and hand me the result in the morning” use case is the canonical thing Routines exist for. I would have spent a weekend building a scheduler, a repo walker, a synthesis prompt harness, and a delivery mechanism, and then I’d have spent every subsequent weekend keeping that scaffolding alive against a moving platform. Instead the answer was: configure a routine. The file never got created. That’s the best possible outcome for a script, that it dies before it costs you anything.

The check is five minutes and I keep skipping it

Here’s the uncomfortable part. None of these were hard to catch. The check is one question asked before you open the editor: is this native yet? Read the changelog. Skim the features index. Ask whether the platform you already depend on has grown a feature that covers the thing you are about to build.

Five minutes. The asymmetry is brutal. Five minutes against a weekend of building plus an open-ended tail of maintenance. And I still skip it, because writing the script feels like progress and reading the docs feels like procrastination. That feeling is exactly backwards. The script is the procrastination. It’s the thing that lets you avoid the harder question of whether the work needs to exist at all.

This is not “never write custom tooling.” It’s “earn it.” Custom infrastructure should be the residue left after you’ve subtracted everything the platform already does, not the first thing you reach for because building is more fun than reading release notes.

Know the boundary or the check turns into dogma

The flip side matters just as much, because “just use the platform” applied without judgment is how you end up contorting your architecture to fit a feature that almost-but-not-quite does the job.

Routines have a hard limit: they run in the cloud, on the vendor’s infrastructure, which means they cannot reach my local network and cannot see uncommitted state. Anything that needs to touch a box on my LAN, read a working tree that hasn’t been pushed, or poke at a service that only exists behind my own firewall stays local. That’s not a knock on Routines. It’s the boundary, and the boundary is what tells you which tooling is legitimately yours to own.

So the rule isn’t “the platform always wins.” It’s: the platform owns everything it can reach; you own the rest. Overnight synthesis of committed, cloud-reachable state? Native. A job that has to ssh into a local-only box and read a half-finished branch? That’s yours, and writing it is correct, not lazy. The check doesn’t tell you to never build. It tells you precisely where the line falls, and the line is “can the native feature physically reach this.”

That clarity is the payoff. Once you know the boundary, you stop arguing with yourself about every script. Local network or uncommitted state on the critical path means build it. Everything else means check native first, and check it hard.

The same review found the inverse mistake

The review that killed those scripts also surfaced the opposite failure: the same blindness pointed the other way. I had no .claude/rules/ directory. The platform ships a lazy-load convention for domain rules: drop markdown files with a paths: frontmatter key and they load on demand when you touch matching files, so the agent gets the right conventions in context without you pasting them into every prompt. I’d been manually prompting domain context like the feature didn’t exist, which is just the build-it-yourself instinct wearing a different hat. Where I’d been over-building, I’d also been under-adopting. Both are the same root cause: not knowing what the platform already does.

The check cuts both directions. “Is this native yet?” isn’t just a way to avoid writing scripts you don’t need. It’s a way to find the features you should already be using and aren’t. You run it before you build, and you run it when something feels harder than it should, because “this is annoying to do by hand” is very often the platform telling you it shipped the fix three releases ago and you missed the note.

Two scripts died with code already in them. A third died before it had any. Nothing of value was lost, because the value was never in the scripts. It was in the five minutes I almost didn’t spend.

So run that check before you open the editor on the next one, and run it in both directions. First, read the changelog and skim the features index for the exact thing you are about to build. Second, ask whether the native feature can physically reach what your job touches, because a cloud-hosted routine cannot see your local network or your uncommitted working tree. If it can reach it, configure it instead of building it. If it cannot, build it and stop second-guessing yourself. Then run the check backwards once: take the task you most dislike doing by hand right now and go find out whether it shipped natively three releases ago.