Tmux Cleanup Session Script | Automatically Kill Unused Tmux Sessions
Do you normally end up with around 15, 20 or more tmux sessions that you manually have to clean up? In this article I show you how I automatically clean up the tmux sessions I haven't used using a bash script, this means that you can run the script in macOS and also in Linux.
Tmux Cleanup Session Script | Automatically Kill Unused Tmux Sessions
Contents
Table of contents
- YouTube video
- Automatically clean up tmux sessions?
- Output of the script
- Script used
- Automatically execute the script on macOS
- Command:
launchctl print - Change interval in which the script is executed
- Other videos mentioned
- You’re a fraud, why do you ask for money, isn’t YouTube Ads enough?
YouTube video
Automatically clean up tmux sessions?
- I use 1 tmux session per project. What is a project? It is each one of my GitHub repos
- What this means is that with a single keymap I can switch between my different projects, or example,
hyper+t+jtakes me to my dotfiles, if I typehyper+t+uit takes me to my notes - Why do I have a project per tmux session? Because I can quickly switch to any of them, immediately open Neovim, start working on that project, and push my changes to GitHub when I’m done
- The problem is that after a few days or weeks, I end up with around 15, or 20 tmux sessions. So I have to manually close them
- To address this, I created a script that automatically kills the tmux sessions that haven’t been accessed in a certain amount of time
- To be honest, I didn’t “create” the script, I modified a script that I found here
- This script was created by the
dhulihanGitHub user
Output of the script
- Below you can see an example of the script in action
1
2
3
4
5
6
7
8
9
❯❯❯❯ cat /tmp/tmuxKillSessions.out
2025-03-12 23:53:21 - Killed session: dotfiles_latest-j (Inactive for 199min)
2025-03-12 23:53:21 - Killed session: karabiner_rules (Inactive for 199min)
2025-03-12 23:53:21 - Killed session: linkarzu-h (Inactive for 199min)
2025-03-12 23:53:21 - Killed session: obsidian_main-u (Inactive for 199min)
2025-03-13 09:04:59 - Killed session: karabiner_rules (Inactive for 204min)
2025-03-13 11:31:34 - Killed session: scripts_public- (Inactive for 224min)
2025-03-13 14:05:16 - Killed session: dotfiles_latest-j (Inactive for 176min)
2025-03-13 14:05:16 - Killed session: obsidian_main-u (Inactive for 132min)
- Notice above that it has killed the sessions that have not been accessed for the amount of time specified in parentheses, I.E.
(Inactive for 132min) - Notice that this file is stored in the
tmpdirectory, which means it will be automatically deleted after a reboot
Script used
- The script can be found below, just keep in mind that this may change in the future if I decide to update it, so to find the latest version, you can go to my dotfiles
- Script found here: linkarzu/tmuxKillSessions.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env bash
# I (linkarzu) did not come up with this script, It's a slightly modified
# version of https://gist.github.com/dhulihan/4c65e868851660fb0d8bfa2d059e7967
# by github user dhulihan
# If I do not add this, the script will not find tmux or any other apps in
# the /opt/homebrew/bin dir. So it will not run the tmux ls command
export PATH="/opt/homebrew/bin:$PATH"
# I make this slightly lower than the LaunchAgent interval
TOO_OLD_THRESHOLD_MIN=110
TMUX_LOG_PATH="/tmp/tmuxKillSessions.log"
NOW=$(($(date +%s)))
tmux ls -F '#{session_name} #{session_activity}' | while read -r LINE; do
SESSION_NAME=$(echo $LINE | awk '{print $1}')
LAST_ACTIVITY=$(echo $LINE | awk '{print $2}')
LAST_ACTIVITY_MINS_ELAPSED=$(((NOW - LAST_ACTIVITY) / 60))
# # print all sessions
# echo "${SESSION_NAME} is ${LAST_ACTIVITY_MINS_ELAPSED}min"
if [[ "$LAST_ACTIVITY_MINS_ELAPSED" -gt "$TOO_OLD_THRESHOLD_MIN" ]]; then
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "$TIMESTAMP - Killed session: $SESSION_NAME (Inactive for ${LAST_ACTIVITY_MINS_ELAPSED}min)" | tee -a $TMUX_LOG_PATH
tmux kill-session -t ${SESSION_NAME}
# In case you want to test the script without killing sessions, comment the 2 lines above and uncomment below
# echo "${SESSION_NAME} is ${LAST_ACTIVITY_MINS_ELAPSED}min inactive and would be killed."
fi
done
- Notice that the command that does the trick is this one
1
tmux ls -F '#{session_name} #{session_activity}'
1
2
3
4
5
6
❯❯❯❯ tmux ls -F '#{session_name} #{session_activity}'
SSH-storage3-k 1741920331
dotfiles_latest-j 1741923284
linkarzu-h 1741923338
linkarzu_github_io- 1741923336
obsidian_main-u 1741922438
- It shows you the name of the session, and it’s last activity time, then we just compare if the last activity is higher than our threshold
TOO_OLD_THRESHOLD_MIN(notice that mine is set to 110 minutes) we kill the session - You could manually execute this script, but that wouldn’t make any sense, so instead, we’ll configure macOS to execute the script every 2 hours
- If you’re on Linux, all you need to do is to setup a cron job and you’re good to go
- In macOS it’s a bit trickier but it can be achieved creating a launch agent.
Automatically execute the script on macOS
- Remember, as I mentioned above, we’ll do this using a
LaunchAgent - I don’t like creating it manually, so I just add the code below to my
.zshrcfile, and if the file does not exist, it will create it. If the plist file is not loaded, it will load it
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# Automate tmux session cleanup every X hours using a LaunchAgent
# This will create plist file to run the script every X hours
# and log output/errors to /tmp/$PLIST_LABEL.out and /tmp/$PLIST_LABEL.err
# NOTE: If you modify the INTERVAL_SEC below, make sure to also change it in
# the ~/github/dotfiles-latest/tmux/tools/linkarzu/tmuxKillSessions.sh script
#
# 1 hour = 3600 s
INTERVAL_SEC=7200
PLIST_ID="tmuxKillSessions"
PLIST_NAME="com.linkarzu.$PLIST_ID.plist"
PLIST_LABEL="${PLIST_NAME%.plist}"
PLIST_PATH="$HOME/Library/LaunchAgents/$PLIST_NAME"
SCRIPT_PATH="$HOME/github/dotfiles-latest/tmux/tools/linkarzu/$PLIST_ID.sh"
# Ensure the script file exists
if [ ! -f "$SCRIPT_PATH" ]; then
echo "Error: $SCRIPT_PATH does not exist."
else
# If the PLIST file does not exist, create it
if [ ! -f "$PLIST_PATH" ]; then
echo "Creating $PLIST_PATH..."
cat <<EOF >"$PLIST_PATH"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>$PLIST_LABEL</string>
<key>ProgramArguments</key>
<array>
<string>$SCRIPT_PATH</string>
</array>
<key>StartInterval</key>
<integer>$INTERVAL_SEC</integer>
<key>StandardOutPath</key>
<string>/tmp/$PLIST_ID.out</string>
<key>StandardErrorPath</key>
<string>/tmp/$PLIST_ID.err</string>
</dict>
</plist>
EOF
fi
fi
# Check if the plist is loaded, and load it if not
if ! launchctl list | grep -q "$PLIST_LABEL"; then
echo "Loading $PLIST_PATH..."
launchctl load "$PLIST_PATH"
echo "$PLIST_PATH loaded."
fi
- Do you need to add this to your
.zshrcfile?:- No, you can create the plist file manually, you can see the code that my
~/.zshrcfile generated in~/Library/LaunchAgents/com.linkarzu.tmuxKillSessions.plist
- No, you can create the plist file manually, you can see the code that my
1
cat ~/Library/LaunchAgents/com.linkarzu.tmuxKillSessions.plist
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
linkarzu.@.[25/03/13]
~/Library/LaunchAgents
❯❯❯❯ cat com.linkarzu.tmuxKillSessions.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.linkarzu.tmuxKillSessions</string>
<key>ProgramArguments</key>
<array>
<string>/Users/linkarzu/github/dotfiles-latest/tmux/tools/linkarzu/tmuxKillSessions.sh</string>
</array>
<key>StartInterval</key>
<integer>7200</integer>
<key>StandardOutPath</key>
<string>/tmp/tmuxKillSessions.out</string>
<key>StandardErrorPath</key>
<string>/tmp/tmuxKillSessions.err</string>
</dict>
</plist>
- Only if you are adding the plist file manually, load it with the command below:
1
launchctl load ~/Library/LaunchAgents/com.linkarzu.tmuxKillSessions.plist
- Why do I add this to my
.zshrcfile? - If you pay attention to the file, there’s a lot of verification steps I run, and I initialize a lot of the tools that I use. It’s a mess, I have to separate it in multiple files to make it easier to understand, but I set it up so long ago and it just works as of now, so why bother
- Again, remember that the latest version of the code shown above is always going to be in my dotfiles, for now, my zshrc file can be found here:
- Another big reason why my
zshrcfile is a bit complex, is because it helps me setup a new mac computer relatively easily. - I have a script that I execute when I get a new mac and I go over that in detail in this video:
Command: launchctl print
- If I run this command, it shows me a lot of details about this
launchctlagent
1
launchctl print gui/$(id -u)/com.linkarzu.tmuxKillSessions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
linkarzu.@.[25/03/13]
~/Library/LaunchAgents
❯❯❯❯ launchctl print gui/$(id -u)/com.linkarzu.tmuxKillSessions
gui/501/com.linkarzu.tmuxKillSessions = {
active count = 0
path = /Users/linkarzu/Library/LaunchAgents/com.linkarzu.tmuxKillSessions.plist
type = LaunchAgent
state = not running
domain = gui/501 [100012]
asid = 100012
minimum runtime = 10
exit timeout = 5
runs = 7
last exit code = 0
spawn type = daemon (3)
jetsam priority = 40
jetsam memory limit (active) = (unlimited)
jetsam memory limit (inactive) = (unlimited)
jetsamproperties category = daemon
jetsam thread limit = 32
cpumon = default
run interval = 7200 seconds
probabilistic guard malloc policy = {
activation rate = 1/1000
sample rate = 1/0
}
properties = inferred program | system service | managed LWCR | tle system
}
- Notice I can see that it has been executed 7 times
runs = 7 - I can also see the interval
run interval = 7200 secondsand many more details - This quickly helps you to see if the launch agent is working or not
Change interval in which the script is executed
If adding the code to your zshrc file
- First delete the existing plist file
1
rm ~/Library/LaunchAgents/com.linkarzu.tmuxKillSessions.plist
- Then, re-create the file (in my case I just source my
zshrcfile)
1
source ~/.zshrc
- Then I unload the plist file
1
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.linkarzu.tmuxKillSessions.plist
- Source my
zshrcfile again
1
source ~/.zshrc
If creating the plist file manually
- Just modify the file directly
- Then you’ll probably have to unload it
1
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.linkarzu.tmuxKillSessions.plist
- And then load it again
1
launchctl load ~/Library/LaunchAgents/com.linkarzu.tmuxKillSessions.plist
Other videos mentioned
You’re a fraud, why do you ask for money, isn’t YouTube Ads enough?
- I explain all of this in the “about me page” link below:
- youre-a-fraud-why-do-you-ask-for-money-isnt-youtube-ads-enough
- Above you’ll also find links to my discord, social media, etc
This post is licensed under CC BY 4.0 by the author.
