Three custom scripts were going to live in our agent toolkit, and two of them already had code. By the end of one week all three were dead, and the reason was the same each time. We were about to hand-roll something the platform had already shipped. It was sitting in the docs while we opened a new file and started typing an import block.
What killed them was a five-minute check we keep forgetting to run. Every time we skip it, we spend a weekend on a problem that was never ours to solve.
The two that already had code
The first was a cost tracker. We run a fleet of LLM providers behind a router, and we wanted spend awareness built in, so we wrote cost_tracker.py. 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. Both were real files with real logic, and writing them felt productive because we could watch them work in a test.
Both got deleted in the same review. The logic held up, but they sat at the wrong altitude. They solved 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 own forever. You patch it, you debug it at 1 AM when a provider changes its error envelope, and you carry it through every refactor. It is maintenance debt the moment it merges, and the only thing that justifies that debt is that nothing else can do the job. Here, something else already did.
The one that never got written
The last one was going to be digest_job.py. The pitch was clean: every night, walk the repos, synthesize what moved, and leave a digest to read with coffee. We had the shape of it in our heads and we were a tab away from creating the file.
Then we looked at what the platform shipped, and Claude Code Routines covered the exact need. They are cloud-hosted scheduled tasks running on the vendor’s infrastructure, and “run a synthesis job overnight and hand over the result in the morning” is the canonical thing they exist for. We would have spent a weekend building a scheduler, a repo walker, a synthesis prompt harness and a delivery mechanism. After that we would have spent every later weekend keeping the scaffolding alive against a moving platform. Instead the answer was to configure a routine. The file never got created, which is the best outcome a script can have, because it died before it cost anything.
Five minutes, skipped every time
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. See whether the platform you already depend on has grown a feature that covers the thing you are about to build.
Five minutes goes up against a weekend of building plus an open-ended tail of maintenance. We still skip it, because writing the script feels like progress and reading the docs feels like procrastination. That feeling is backwards. The script is the procrastination, since it lets you avoid asking whether the work needs to exist at all.
None of this means never write custom tooling. It means earning it. Custom infrastructure should be what is left after you subtract everything the platform already does, and it should not be the first thing you reach for because building is more fun than reading release notes.
Where the native feature stops
Applying “just use the platform” without judgment is how you end up contorting your architecture around a feature that almost does the job.
Routines have a hard limit. They run in the cloud, on the vendor’s infrastructure, so they cannot reach our local network and cannot see uncommitted state. Anything that touches a box on our LAN, reads a working tree that hasn’t been pushed, or pokes at a service that only exists behind our own firewall stays local. That is simply the boundary, and it tells you which tooling is legitimately yours to own.
So the platform owns everything it can reach, and we own the rest. Overnight synthesis of committed, cloud-reachable state is native. A job that has to ssh into a local-only box and read a half-finished branch is ours, and writing it is correct. The line falls where the native feature can physically reach.
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, pointed the other way
The review that killed those scripts also surfaced the opposite failure. We 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. We had been manually prompting domain context as if the feature didn’t exist, which is the build-it-yourself instinct wearing a different hat. Where we had been over-building, we had also been under-adopting.
The check cuts both directions. “Is this native yet?” saves you from scripts you don’t need, and it turns up features you should already be using. Run it before you build, and run it when something feels harder than it should. “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, and the 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 we almost didn’t spend.
What to run before approving a build
Before approving a build, ask whether the platform you already depend on ships the feature natively, and read the changelog and features index first. Then ask whether that native feature can physically reach what the job touches. A cloud-hosted routine cannot see a local network or an uncommitted working tree. If it can reach it, configure it. If it cannot, build it. Then run the check in reverse on a task still done by hand, and find out whether the platform shipped a native fix for it.