The setup
semak.khursani.dev tracks transit disruptions for Klang Valley riders. The reports come from two places: Rapid KL's official Facebook posts, and rider posts on Threads. A post like "LRT kelana jaya dah berhenti" should appear on the board within minutes of being posted.
There is no API for this. Prasarana publishes no realtime rail feed - the government's open data portal carries bus positions only. So the pipeline reads Threads the way a person does: with a real browser, driven by my AI twin.
The twin is a Claude Code agent. It runs the scraper, the review gates, the board, the deploys - on a shared server with no screen.
The week my Chrome died three times
Earlier this month the transit board went quiet. Not quiet as in calm - quiet as in empty. Riders were posting packed LRT platforms on Threads and the board showed nothing. The scraper logged zero captured posts, every check reported green, and nobody - not me, not the twin - flagged it for a day and a half.
When I finally dug in, the failures stacked up like this:
- The pipeline closed my actual Chrome after every pass. The scraper ran "stop all sessions" when it finished. On my laptop that closed the whole browser, every twenty minutes, three days in a row. The board went blind each time and I blamed the network.
- An error message pretended to be a session id. When the scraper asked for a browser session and got an error back, it kept the error JSON as the session id and carried on, parsing zero posts with total confidence.
- A vision judge called packed trains empty. Image posts of packed platforms ran through an image check that answered "not crowded", so they never became reports. One image it checked really was a dirt bike in the mountains - that call was correct.
- The review gate wanted journalism. Posts like "KJ line pun stress hari2" or a photo of a full carriage failed a bar that demanded a first-hand incident report written like a news wire.
- Cloudflare reset the websocket every couple of hours. The browser paired to the server through a proxied domain, and the proxy killed the connection on a schedule.
None of these logged an error. Every one of them looked like a quiet day.
The fixes
Each failure got a specific repair, verified against the live system:
- Sessions are reused, never stopped. One browser session stays open and the lanes share it.
- Failures are loud. A session request that fails now stops the pass with the daemon's own message in the log.
- A watchdog counts dry capture cycles and pushes a notification every hour until a human looks.
- The vision judge counts rush-hour full as crowded, and ties break toward reporting.
- The review gate accepts any rider voice - complaints, crowding photos, warnings to other riders - and rejects only spam and noise.
- The always-on browser moved onto the server itself: the extension built from its own public source, loaded into Chromium on a virtual display, paired over loopback so no proxy sits in the path. My laptop is no longer part of the system.
After the last fix, the board captured 46 posts in a single pass, kept the real reports, and rendered them within minutes. The browser survived the pass. That sentence took three days to become true.
What still cannot be fixed
The official rail feed does not exist. Prasarana publishes bus positions through the national open data portal and nothing for rail - no service alerts, no vehicle positions, no API. Human reports plus official Facebook posts are the state of the art for Malaysian rail disruption data, and they will stay that way until an operator decides riders deserve a feed.
The point of a twin is not that it never breaks. It is that every break has a cause, every cause is findable, and every fix is a small honest change that ships with its evidence. The board went quiet because six small things were wrong at once. They were all findable. They are all fixed now, and each one has a monitor watching for its return.