Extend the Timeout. Fix the Schedule. Yes, In That Order.

The instinct is to treat a longer timeout as a band aid. You see “retry on RuntimeError” in a diff and assume the author did not understand the actual failure. Nine times out of ten, that read is right. This is the tenth time.

The medium mirror job runs hourly. It opens a headless Chrome session via CDP, logs into Medium, and mirrors posts there. The launch deadline was 15 seconds. On an idle machine, that is generous. The machine is not idle.

Content generation and publish jobs share the same launchd schedule window. A generate job spins up an LLM subprocess and burns CPU. The publish job fires at the same time. Chrome starts cold, competes for every cycle, and misses the 15 second window. The session() call raises. The mirror run aborts. No post mirrored. No recovery. Just a dead entry in the logs and a gap in the syndication history.

Obvious fix: bump the deadline. I set it to 45 seconds, which covers any realistic contention scenario without letting a genuinely broken launch hang forever.

That solves the timeout problem but not the cleanup problem. When Chrome fails to start, it does not always clean up after itself. It leaves a half dead process squatting the port and locking the profile directory. The next run tries to open the same profile, hits the lock, fails again, and you are exactly where you started.

So the full fix is two parts. 45 second deadline. On RuntimeError, kill any Chrome process holding the target port and profile directory, then retry once. Not a loop. One retry. If it fails twice you are not looking at schedule contention anymore and retrying blindly is just burning clock.

The tradeoff is worth naming clearly. A clean 15 second abort is loud. It shows up in the logs immediately, which is uncomfortable and exactly what you want. A 45 second deadline with a silent retry mostly succeeds, which is the goal, but it hides the pressure building underneath. If the machine gets busier you extend to 90 seconds next time, then 120. You have now built a timeout ladder whose only purpose is compensating for a schedule you never fixed.

The schedule is the actual problem. The generate, publish, and mirror jobs overlap because I have not staggered them. Staggering them takes about five minutes in the launchd plists and eliminates the contention entirely. I should have done that first.

I did not do that first because the timeout plus retry lands in one commit, works immediately, and ships now. Fixing the schedule requires auditing which jobs fire at which cadences and what they compete for, and that is a separate session.

Ship the retry. Fix the schedules. The order matters for velocity; both matter for correctness. Compensating controls are fine to ship. They are not fine to leave as the final answer. The retry buys you time to go back and do the thing you should have done first. Use the time.

Write a comment