Skip to content

Checking the gate with one command

Adityo Guni Waluyo

What used to take four MCP round trips and an 86KB payload at session start is now one read-only script with semantic exit codes.

TL;DR

Checking status used to need four heavy MCP calls, and caching only created stale-data headaches. The fix was a lightweight read-only script that runs four checks in parallel with clear exit codes for ready, drift, or infra trouble. It handles quirks like Cloudflare blocks and rate limits while staying local for quick iteration and always-fresh data.

Every work session used to start with the same ritual for me: open the board, wait for it to load, then fire a series of MCP calls just to confirm everything still follows the rules. Four round trips back and forth, and one work-item payload weighing 86KB, all of that just to read a status. It felt like driving a car that needs five starter cranks before the engine actually runs.

The First Guess That Missed

At first I thought the solution was moving all validation logic into the MCP itself. Or at least building a cache layer so I wouldn't pull the same data over and over. The logic sounded reasonable: if the data is heavy, store it locally.

But once I tried it, the approach only added complexity. Caches go stale. Syncing state between the MCP and the main board created a new kind of confusion. I spent more time making sure the cache was accurate than doing the actual work.

Turns out, only the reading side of the gate needed replacing. State mutations or adding evidence comments still go through the MCP, per the original design. And for reading status, fetching fresh data every time feels much better than babysitting a cache that may already be stale.

Read Fresh, Execute in Parallel

So I wrote scripts/plane_status.py. One script, one read-only REST pass, no cache. Four section checks run in parallel using ThreadPoolExecutor from the concurrent.futures module [5], namely work items, decisions freshness, the Pages mirror, and modules. Because they run together on a thread pool, the wait is far shorter than checking them one by one.

The detail I like most: the exit codes are semantic. Code 0 means everything is clean. Code 1 is an alarm, say drift or work-in-progress breaching the limit of 2. Code 2 means the infrastructure itself is in trouble. The result becomes machine-readable, no human eyes needed to scan the board.

Two quirks showed up while writing it. First, Cloudflare likes to block the default python-urllib User-Agent with error 1010, so the script just sends a curl User-Agent. Second, the script probes candidate base URLs: try loopback first, then fall over to the tunnel if the first one doesn't answer. The board itself is self-hosted Plane, an open-source project management platform [6] behind that tunnel.

One more thing: on a 429 response, the script honors the server's Retry-After header [7]. It waits as instructed instead of hammering the server with retries.

A Gate That's Cheap to Check Is a Gate That Gets Kept

A gate you can't check with one command ends up being a gate you stop checking. The most expensive part of a gate isn't its rules, it's the friction of having to look at the status. Now it's one command, and I immediately know whether the working environment is ready or something has drifted past the 2-second tolerance.

I deliberately keep the script local in the internal repo, not moved to the project's main repo. Validation needs can change fast, and that flexibility matters to keep iteration quick. Sometimes the best solution isn't the most sophisticated architecture, it's the one that stays out of the way of doing the right thing. And the key: data that is always fresh.

Sources

Related articles