The Claude Code Changelog
All Episodes
Preventing Premature Runner Shutdowns in Claude Code 2.1.311

Preventing Premature Runner Shutdowns in Claude Code 2.1.311

0:00|0:00

Learn how Claude Code 2.1.311's new self-hosted runner idle-hold flags prevent background processes and dev servers from being terminated prematurely. We explore how to configure these settings, avoid common timing misconfigurations, and handle teardown caveats.

Show Notes


Chapter 1

Hold Idle Sessions Open on Self Hosted Runners

Lachlan Reed

Right, so picture this. Your agent finishes a turn, the dev server it started is humming along in the background, and the runner decides the session is idle and packs it up. Server gone. That's what today's about. This is The Claude Code Changelog, brought to you by Jellypod AI, and I'm Lachlan.

James Turner

And I'm James. Claude Code 2.1.311 added two flags to the self hosted runner that fix exactly that. First one is the hold idle file flag, with the environment variable SELF_HOSTED_RUNNER_HOLD_IDLE_FILE. Second is the max hold min flag, with SELF_HOSTED_RUNNER_MAX_HOLD_MS. Quick scope note: self hosted environments are in public beta on Team and Enterprise plans, so this is for platform and DevOps folks running their own runners.

Lachlan Reed

And the problem is pretty specific, yeah? Before this, when a session finished a turn with no interactive command in flight, the runner treated it as idle. Then it released or drained it, based on the release idle session min setting or your autoscaler's shutdown policy. But devs spawn background stuff all the time. Local dev servers, a mock API, Docker test containers. Runner can't tell those matter.

James Turner

Right, and that's the gap the new flag fills. You point it at a file path. The docs describe it as a file your host keeps changing to hold an idle session on the runner. So the runner isn't guessing about your processes. Your host is the one that knows whether the mock API is still up, and it signals that by touching or updating that file. While the file keeps changing, the idle session stays put. When the file goes quiet, normal idle release takes over.

Lachlan Reed

So the runner's basically asking, mate, are we still busy? And your script answers by poking a file.

James Turner

Pretty much. Here's a sketch of how you'd wire it. This is my illustration, not something from the docs. You run a small loop on the host that checks whether your background service is alive, and if it is, it touches the file every so often. Then you start the runner with the hold idle file flag pointing at that path, or set SELF_HOSTED_RUNNER_HOLD_IDLE_FILE in your service definition. If both the flag and the environment variable are set, the flag wins, which is true for most runner options.

Lachlan Reed

Now the bit that'll save you a nasty surprise. The max hold min flag. Default is 1440 minutes, so twenty four hours. It releases a held session anyway, that many minutes after it went idle. Because otherwise some forgotten process holds a runner host hostage forever, and you're paying for a box doing nothing.

James Turner

And two edge cases worth memorizing. Setting it to zero turns the hold off entirely. And if the value is at or below the release idle session min setting, nothing gets held at all. So if you set max hold to ten minutes and your idle release is fifteen, you've configured a hold that never happens. Check those two numbers against each other. Also, the env var is in milliseconds, with that MS suffix, while the flag is in minutes. Easy to get wrong in a Helm value.

Lachlan Reed

Caveats, then. First, container isolation. In a setup where each session gets its own ephemeral container, that teardown now waits until the hold releases. So a held session can sit there for up to your max hold window. Second, stop timeouts. If your host sends a stop signal and your timeout is shorter than the hold windows you've allowed, the host might kill things before the runner finishes its shutdown. So raise those together.

James Turner

And this connects nicely to the Monitor episode, 2.1.310, where we talked about replacing polling. Monitor is about the session watching a background process. This is the runner infrastructure side, making sure the process is still there when the session comes back for it. Also, it needs 2.1.311 or later on the runner, so check your fleet versions first.

Lachlan Reed

A few smaller bits from 2.1.311 and 2.1.312. The Claude apps gateway gets an oidc.user_id_claim setting, so if you're on Entra ID you can identify users by the oid claim instead of sub. Full screen mode now has click to send for queued prompts. And the setup scripts for cloud environments now get a bash editor with syntax highlighting.

James Turner

That's the episode. If you run self hosted runners, today's job is simple: pick your max hold value, make sure it's above your idle release threshold, and raise your stop timeout to match.