Claude Code in Remote Sessions

October 28, 2026

Disclaimer: This post was written with the assistance of Anthropic’s Claude Opus 5.5 model, which also helped write the function it describes.

At the moment, I run Claude Code sessions on remote machines over SSH: two Linux VMs and a Mac. A session often works for a long time on a task, and I do not want it to end when the SSH connection drops or when I close the laptop. I also want to come back to the same session later, from the same or from another computer.

Why dtach

The usual tools for this are tmux and screen. I use dtach instead, because it is simpler. It does only one thing: it runs a program detached from the terminal and lets you attach to it again later. There are no windows, panes or key bindings to learn, and the terminal works as usual. Each session is a socket file, and you attach to it by giving the same socket again.

What cdt Does

cdt is a small shell function. cdt NAME attaches to the Claude Code session of the project NAME, or starts it if it is not running:

  • NAME is a directory under ~/projects. Without a name, it uses the name of the current directory.
  • The session always starts in the project directory, so it does not matter where I run cdt from.
  • Each project has its own dtach socket in ~/.dtach, so there is one session per project.
  • When the project already has a conversation, the new session continues the latest one. Otherwise it starts a new conversation.
  • Additional arguments are passed to Claude Code, but only when a session is started.

To get back to a session, I run the same cdt command again.

Things That Were Not Obvious

  • Finding the previous conversation. Claude Code keeps the conversations of a directory under ~/.claude/projects/, in a folder named after the directory’s path with every character other than letters and digits replaced by -. If this folder has a conversation, cdt starts Claude Code with --continue.
  • Backgrounded sessions. When I press Ctrl+B while a tool is running, Claude Code moves the whole session to its background daemon. While the daemon holds the conversation, --continue cannot take it. So cdt asks Claude Code for a background session in the same directory. If it is idle, cdt stops it and resumes its conversation. If it is busy, cdt attaches to it instead.
  • The suspend key. dtach normally suspends itself when Ctrl+Z is pressed. With -z, Ctrl+Z goes to Claude Code instead.
  • Redrawing after attaching. With -r winch, dtach sends a window size change to Claude Code when I attach, so it redraws the screen.

The Function

This is the bash version. It needs dtach and jq. On the Mac, I use a zsh port of the same code. My own version also sets the telemetry attributes I described in Keeping Track of Claude Code Usage, which I left out here.

# cdt [NAME [CLAUDE-ARGS...]]: attach to Claude Code session NAME, or start
# it under dtach so it survives a dropped SSH connection. NAME is a project
# under ~/projects, defaulting to the current directory's name. The session
# always starts in the project directory; it continues that directory's latest
# conversation when one exists, otherwise it starts a new one. CLAUDE-ARGS
# apply only when a session is started. Detach with Ctrl+\, and run the same
# cdt command again to reattach.
# A conversation held by Claude's background daemon (Ctrl+B while a tool runs
# backgrounds the whole session) blocks --continue, so cdt takes it back: an
# idle one is stopped and resumed here, a busy one is attached to.
cdt()
{
	local name=${1:-$(basename "$PWD")}
	local dir=$HOME/projects/$name
	[ -d "$dir" ] || dir=$PWD
	local sock=$HOME/.dtach/$name
	local -a cmd=()
	if compgen -G "$HOME/.claude/projects/${dir//[^A-Za-z0-9]/-}/*.jsonl" >/dev/null; then
		cmd=(--continue)
	fi
	cmd+=("${@:2}")
	if [ ! -S "$sock" ]; then
		local id sid st pid
		read -r id sid st pid < <(claude agents --json --all | jq -r --arg d "$dir" \
			'first(.[] | select(.kind == "background" and .cwd == $d and .pid != null))
			| "\(.id) \(.sessionId) \(.status) \(.pid)"')
		if [ -n "$id" ] && kill -0 "$pid" 2>/dev/null; then
			if [ "$st" = busy ]; then
				echo "cdt: attaching to busy background session $id"
				cmd=(attach "$id")
			else
				echo "cdt: stopping idle background session $id to resume it here"
				claude stop "$id" && cmd=(--resume "$sid" "${@:2}")
			fi
		fi
	fi
	mkdir -p "$HOME/.dtach"
	(cd "$dir" && dtach -A "$sock" -r winch -z claude "${cmd[@]}")
}

Conclusion

cdt is a small function. I start a task and come back to it later from wherever I am. The session keeps working while I am away, and one command brings me back to it.