1
00:00:00,260 --> 00:00:01,800
Right, so picture this.

2
00:00:02,340 --> 00:00:06,560
Your agent finishes a turn, the dev server
it started is humming along in the

3
00:00:06,600 --> 00:00:11,120
background, and the runner decides the
session is idle and packs it up.

4
00:00:11,800 --> 00:00:12,460
Server gone.

5
00:00:13,220 --> 00:00:14,260
That's what today's about.

6
00:00:14,720 --> 00:00:18,540
This is The Claude Code Changelog, brought
to you by Jellypod AI,

7
00:00:19,020 --> 00:00:19,680
and I'm Lachlan.

8
00:00:20,075 --> 00:00:20,695
And I'm James.

9
00:00:21,275 --> 00:00:27,415
Claude Code 2.1.311 added two flags to the
self hosted runner that fix exactly that.

10
00:00:27,995 --> 00:00:31,815
First one is the hold idle file flag, with
the environment variable

11
00:00:31,895 --> 00:00:34,155
SELF_HOSTED_RUNNER_HOLD_IDLE_FILE.

12
00:00:34,755 --> 00:00:39,395
Second is the max hold min flag, with
SELF_HOSTED_RUNNER_MAX_HOLD_MS.

13
00:00:40,035 --> 00:00:44,495
Quick scope note: self hosted environments
are in public beta on Team and Enterprise

14
00:00:44,555 --> 00:00:48,415
plans, so this is for platform and DevOps
folks running their own runners.

15
00:00:49,325 --> 00:00:51,025
And the problem is pretty specific, yeah?

16
00:00:51,705 --> 00:00:56,405
Before this, when a session finished a
turn with no interactive command in flight,

17
00:00:56,925 --> 00:00:58,605
the runner treated it as idle.

18
00:00:59,245 --> 00:01:04,285
Then it released or drained it, based on
the release idle session min setting or

19
00:01:04,325 --> 00:01:06,205
your autoscaler's shutdown policy.

20
00:01:06,945 --> 00:01:09,945
But devs spawn background stuff all the
time.

21
00:01:10,445 --> 00:01:14,345
Local dev servers, a mock API, Docker test
containers.

22
00:01:15,045 --> 00:01:16,345
Runner can't tell those matter.

23
00:01:17,455 --> 00:01:19,815
Right, and that's the gap the new flag
fills.

24
00:01:20,295 --> 00:01:21,675
You point it at a file path.

25
00:01:22,115 --> 00:01:26,595
The docs describe it as a file your host
keeps changing to hold an idle session on

26
00:01:26,635 --> 00:01:27,055
the runner.

27
00:01:27,595 --> 00:01:29,775
So the runner isn't guessing about your
processes.

28
00:01:30,255 --> 00:01:33,335
Your host is the one that knows whether
the mock API is still up,

29
00:01:33,755 --> 00:01:36,375
and it signals that by touching or
updating that file.

30
00:01:37,175 --> 00:01:39,675
While the file keeps changing, the idle
session stays put.

31
00:01:40,155 --> 00:01:42,695
When the file goes quiet, normal idle
release takes over.

32
00:01:43,432 --> 00:01:46,752
So the runner's basically asking, mate,
are we still busy?

33
00:01:47,492 --> 00:01:49,672
And your script answers by poking a file.

34
00:01:50,357 --> 00:01:50,977
Pretty much.

35
00:01:51,437 --> 00:01:53,117
Here's a sketch of how you'd wire it.

36
00:01:53,357 --> 00:01:55,917
This is my illustration, not something
from the docs.

37
00:01:56,577 --> 00:01:59,977
You run a small loop on the host that
checks whether your background service is

38
00:02:00,037 --> 00:02:03,217
alive, and if it is, it touches the file
every so often.

39
00:02:03,737 --> 00:02:07,777
Then you start the runner with the hold
idle file flag pointing at that path,

40
00:02:08,097 --> 00:02:12,117
or set SELF_HOSTED_RUNNER_HOLD_IDLE_FILE
in your service definition.

41
00:02:12,737 --> 00:02:16,077
If both the flag and the environment
variable are set, the flag wins,

42
00:02:16,237 --> 00:02:17,797
which is true for most runner options.

43
00:02:18,577 --> 00:02:20,877
Now the bit that'll save you a nasty
surprise.

44
00:02:21,317 --> 00:02:23,517
The max hold min flag.

45
00:02:24,157 --> 00:02:27,517
Default is 1440 minutes, so twenty four
hours.

46
00:02:27,977 --> 00:02:31,757
It releases a held session anyway, that
many minutes after it went idle.

47
00:02:32,297 --> 00:02:36,757
Because otherwise some forgotten process
holds a runner host hostage forever,

48
00:02:37,277 --> 00:02:39,057
and you're paying for a box doing nothing.

49
00:02:39,888 --> 00:02:41,848
And two edge cases worth memorizing.

50
00:02:42,228 --> 00:02:44,588
Setting it to zero turns the hold off
entirely.

51
00:02:44,888 --> 00:02:48,628
And if the value is at or below the
release idle session min setting,

52
00:02:49,108 --> 00:02:50,068
nothing gets held at all.

53
00:02:50,608 --> 00:02:54,248
So if you set max hold to ten minutes and
your idle release is fifteen,

54
00:02:54,788 --> 00:02:56,588
you've configured a hold that never
happens.

55
00:02:56,968 --> 00:02:58,508
Check those two numbers against each
other.

56
00:02:59,148 --> 00:03:03,768
Also, the env var is in milliseconds, with
that MS suffix, while the flag is in

57
00:03:03,808 --> 00:03:04,208
minutes.

58
00:03:04,768 --> 00:03:06,208
Easy to get wrong in a Helm value.

59
00:03:06,810 --> 00:03:07,510
Caveats, then.

60
00:03:08,210 --> 00:03:10,210
First, container isolation.

61
00:03:10,810 --> 00:03:14,210
In a setup where each session gets its own
ephemeral container,

62
00:03:14,770 --> 00:03:17,570
that teardown now waits until the hold
releases.

63
00:03:18,130 --> 00:03:21,410
So a held session can sit there for up to
your max hold window.

64
00:03:22,170 --> 00:03:23,690
Second, stop timeouts.

65
00:03:24,310 --> 00:03:28,810
If your host sends a stop signal and your
timeout is shorter than the hold windows

66
00:03:28,850 --> 00:03:33,050
you've allowed, the host might kill things
before the runner finishes its shutdown.

67
00:03:33,530 --> 00:03:34,870
So raise those together.

68
00:03:35,670 --> 00:03:40,790
And this connects nicely to the Monitor
episode, 2.1.310, where we talked about

69
00:03:40,830 --> 00:03:41,850
replacing polling.

70
00:03:42,490 --> 00:03:45,570
Monitor is about the session watching a
background process.

71
00:03:45,970 --> 00:03:50,330
This is the runner infrastructure side,
making sure the process is still there when

72
00:03:50,370 --> 00:03:51,610
the session comes back for it.

73
00:03:52,330 --> 00:03:57,970
Also, it needs 2.1.311 or later on the
runner, so check your fleet versions first.

74
00:03:58,978 --> 00:04:03,418
A few smaller bits from 2.1.311 and
2.1.312.

75
00:04:04,118 --> 00:04:08,898
The Claude apps gateway gets an
oidc.user_id_claim setting,

76
00:04:09,438 --> 00:04:14,478
so if you're on Entra ID you can identify
users by the oid claim instead of sub.

77
00:04:15,218 --> 00:04:18,258
Full screen mode now has click to send for
queued prompts.

78
00:04:18,737 --> 00:04:22,618
And the setup scripts for cloud
environments now get a bash editor with syntax

79
00:04:22,658 --> 00:04:23,158
highlighting.

80
00:04:23,810 --> 00:04:24,530
That's the episode.

81
00:04:24,990 --> 00:04:29,090
If you run self hosted runners, today's
job is simple: pick your max hold value,

82
00:04:29,530 --> 00:04:33,390
make sure it's above your idle release
threshold, and raise your stop timeout to

83
00:04:33,450 --> 00:04:33,730
match.

