Detect and revive Claude Code remote-control connections
Project description
claude-connection-keeper
Detect when a Claude Code session's remote-control connection has gone quiet, and optionally nudge it back to life — best-effort, fail-conservative, scriptable.
Unofficial. This is a community tool, not affiliated with, endorsed by, or supported by Anthropic. It relies on undocumented Claude Code internals (see the disclaimer below) and can break on any release. "Claude" and "Claude Code" are trademarks of Anthropic.
Interactive claude sessions left idle frequently show up as disconnected in the
web/mobile UI even though the process is still running. There is no official
claude remote-control status command. This tool reads the signal Claude Code already
writes to disk and, for sessions running in tmux, can re-issue the /remote-control
command for you.
$ cck detect
PID STATE CONF STATUS PANE RECON CWD
2166971 connected high idle Main:5.2 no ~/projects/api
3222329 disconnected medium idle - no ~/projects/web
4190335 connected high idle Main:8.1 no ~/projects/dotfiles
⚠️ Read this first: what the signal actually means
Detection is based on the bridgeSessionId field in each session's per-pid state file.
This is a persisted association, not a live health check. It reliably marks that a
bridge session was set up and not yet torn down — but it has been observed lagging the
real connection state (a session displaying /remote-control is active while its on-disk
bridgeSessionId was already cleared).
Consequences, by design:
- A
connectedverdict is high-confidence. Adisconnectedverdict is medium-confidence — strong, but treat it as "probably disconnected," not "certainly." - Anything the tool can't read cleanly becomes
unknownand is never acted on. - This is all reverse-engineered from undocumented internals and can break on any Claude Code release. It fails safe (does nothing) when it doesn't recognize what it sees. The full empirical basis — what's observed vs. inferred, version stability, the anomaly behind the medium-confidence caveat — is written up in docs/remote-control-internals.md.
If you need certainty, use this as a hint and confirm in the UI.
Install
Single file (zero dependencies, stdlib only):
curl -fsSL https://raw.githubusercontent.com/lfstokols/claude-connection-keeper/main/claude_connection_keeper.py -o cck.py
python3 cck.py detect
As a command on your PATH (recommended):
pipx install claude-connection-keeper
# provides both `cck` and `claude-connection-keeper`
Requires Python 3.8+. Reconnect additionally requires tmux.
Usage
Detect (read-only, the default verb)
cck detect # table of all live sessions
cck detect --json # one JSON object per line, for pipelines
cck detect --state disconnected # filter by state
cck detect --reconnectable # only sessions eligible for reconnect
detect is grep-like: exit code 0 if any session matched, 1 if none. Combined
with --state/--reconnectable that makes it easy to branch in scripts:
if cck detect --reconnectable >/dev/null; then
echo "there are sessions to revive"
fi
Each --json record carries the raw evidence (bridgeSessionId, status, kind,
entrypoint, reasons, …) so you can apply your own thresholds instead of trusting the
built-in verdict.
Reconnect (opt-in, never automatic)
Reconnect injects Ctrl-S (to stash any pending prompt text), then /remote-control,
then Enter into the session's tmux pane.
cck reconnect # DRY RUN — prints what it would do, sends nothing
cck reconnect --apply # actually inject the keystrokes
cck reconnect --apply --pid 12345 # restrict to specific pid(s)
Dry run is the default. You must pass --apply to send keystrokes.
When you name a pid with --pid, a pid that isn't a live, eligible target is reported on
stderr with the reason (e.g. skipped pid=12345: state is connected, not disconnected) —
an explicit request never fails silently.
A session is only eligible when all of these hold (allowlist, not denylist):
| Requirement | Why |
|---|---|
state is disconnected |
nothing to do otherwise |
kind == interactive and entrypoint == cli |
excludes SDK subagents and background jobs — injecting into those would type into another agent's stdin |
status in the allowlist (default: idle) |
busy is mid-work, waiting has a prompt open, shell is in a subshell |
| a tmux pane resolves for the pid | reconnect is tmux-only in v1 |
Loosen the status allowlist deliberately if you know what you're doing:
cck reconnect --apply --statuses idle,waiting
Unrecognized status values (typos) are warned about on stderr and ignored, rather than
silently leaving every session ineligible — only busy, shell, idle, and waiting
are accepted.
Cron recipe
There's no built-in daemon (by design — v1 keeps the acting surface small). A cron job is all you need to keep sessions revived:
# every 4 minutes, revive any eligible disconnected tmux sessions
*/4 * * * * /usr/bin/env cck reconnect --apply >> "$HOME/.claude/cck-reconnect.log" 2>&1
Each run writes one timestamped heartbeat line plus a line per reconnect, e.g.:
2026-05-31T02:56:08-04:00 reconnected pid=2931314 pane=Main:2.1 status=idle
2026-05-31T02:56:08-04:00 cycle checked=14 connected=9 disconnected=4 unknown=1 eligible=1 reconnected=1 failed=0
The heartbeat fires every cycle, so an unchanging log means cron has stopped, not "nothing
to do" — the two are otherwise indistinguishable. Pass --quiet to drop the heartbeat and log
only reconnects/failures (smaller log, but you lose that signal).
Notes:
- cron usually finds the tmux server via the default per-uid socket even without
$TMUXset; ifcckcan't see panes from cron, run the job from a tmux-aware login shell or point it at the socket withtmux -S. - The heartbeat is one line every 4 min (~360/day) — add a
logrotaterule or trim periodically. - Start with
cck reconnect(dry run) in the crontab and read the log for a day before switching to--apply.
Don't use tmux? (the tool still helps)
Detection is universal — cck detect reports every live session no matter how claude
was launched. Only the reconnect step needs a way to type into the session, and that's
the last mile that depends on your setup.
The deal we're hoping works for you: the slow, fiddly part — working out when it's safe
to act and what to send — is the same no matter what you run, so we've tried to do it
once and hand it over, leaving you just the last mile. That last mile is usually a small,
well-scoped job you can hand to Claude. And if you get it working, please send it back —
a PR, or even just your snippet pasted into an issue, means the next person with your setup
isn't starting cold. (Today the tmux path is two small functions, resolve_pane and
send_reconnect; turning that pair into a pluggable backend is itself a very welcome PR.)
Here's the head start we've tried to give you, so you're not beginning from scratch:
-
Which sessions are safe to touch. It took some care to pin down when acting is safe and when it really isn't — a session mid-work, or one with a permission prompt open. That logic doesn't depend on your terminal, so
cck detect --json --reconnectablealready narrows to just the eligible targets. Take it as-is, or check our reasoning in the eligibility table above. -
What to send. The exact reconnect sequence, including the timing we landed on by trial and error:
step bytes why 1 Ctrl-Sstash any prompt text you'd left half-typed, so we don't clobber it 2 literal /remote-controlthe reconnect command, sent as literal text 3 Entersubmit — ~0.2s between each the TUI seems to need a beat per key — too fast and the stash races the text, or Enter fires before the command lands
That leaves transport: getting those keystrokes to the right session. Most modern
terminals and multiplexers expose something for it — kitten @ send-text,
wezterm cli send-text, screen -X stuff, zellij action write-chars, iTerm2's
scripting API. Map the pid to a target, send the sequence above, and that's the whole job.
Not up for it? Fair enough — but opening an issue with your setup and why the tool doesn't fit is genuinely the most useful thing you can do. It's how we work out which setups are common enough to build into the happy path.
How it works
- Discover — read per-pid JSON files from
~/.claude/sessions/(honorsCLAUDE_CONFIG_DIR), keeping only sessions whose pid is alive. - Classify —
bridgeSessionIdpresent →connected; absent/empty but a knownstatusis present →disconnected; otherwise →unknown. - Map to a pane — the session file is keyed on the
claudepid, but the tmuxpane_pidis its parent shell. We walk the parent chain upward until a pid matches a tmux pane (handles wrapper layers), so we know exactly which pane to type into. - Reconnect (opt-in) —
tmux send-keysthe stash +/remote-control+ Enter.
The library functions (discover_sessions, classify, resolve_pane, send_reconnect,
build_reports) are importable if you want to build something else on top of detection.
For the underlying reverse-engineering — session file schema, the bridgeSessionId
set/clear paths, version stability, and confidence ratings per claim — see
docs/remote-control-internals.md.
Limitations
- tmux only for reconnect today. Detection works regardless of how
claudewas launched; reconnect just needs some way to type into the session. If you're not on tmux, see Don't use tmux? — the last mile is meant to be filled in for other setups, and we'd love the PR. - Undocumented + unstable. Signal shape can change between Claude Code releases. The tool fails safe when the shape is unrecognized.
disconnectedis medium-confidence (see the disclaimer above).- No session-name filtering yet beyond
--pid. Planned for a later version.
Development
python3 -m venv .venv && .venv/bin/pip install -e '.[dev]'
.venv/bin/pytest
The test suite monkeypatches all system touchpoints (pid liveness, parent walk, tmux), so it's hermetic and runs anywhere.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file claude_connection_keeper-0.1.0.tar.gz.
File metadata
- Download URL: claude_connection_keeper-0.1.0.tar.gz
- Upload date:
- Size: 19.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f369ac2757cb4ddaf983ce2d33abf1120319e7447e11749f1df64f56de09e257
|
|
| MD5 |
f2bc9301ab3d0e59eac7e24f37b27309
|
|
| BLAKE2b-256 |
1a3aab40ecd08a025a267c51183fd5c1eec273d009d27fd653fabe8a1c6ca090
|
Provenance
The following attestation bundles were made for claude_connection_keeper-0.1.0.tar.gz:
Publisher:
release.yml on lfstokols/claude-connection-keeper
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
claude_connection_keeper-0.1.0.tar.gz -
Subject digest:
f369ac2757cb4ddaf983ce2d33abf1120319e7447e11749f1df64f56de09e257 - Sigstore transparency entry: 1688046222
- Sigstore integration time:
-
Permalink:
lfstokols/claude-connection-keeper@e3139f92ef7e9f94a0a806d0c7a6641ac249ea0e -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/lfstokols
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e3139f92ef7e9f94a0a806d0c7a6641ac249ea0e -
Trigger Event:
push
-
Statement type:
File details
Details for the file claude_connection_keeper-0.1.0-py3-none-any.whl.
File metadata
- Download URL: claude_connection_keeper-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c884b6ea2e2c6474214334728e88e372e03fe83c00b329f0ee3e65bbc77d7dda
|
|
| MD5 |
9969d3584d5e966f3db4d712236ae4b4
|
|
| BLAKE2b-256 |
21b36c863d68c19dffc4d30fbebef8695214468697eb42a0ddcfdcb20a37998b
|
Provenance
The following attestation bundles were made for claude_connection_keeper-0.1.0-py3-none-any.whl:
Publisher:
release.yml on lfstokols/claude-connection-keeper
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
claude_connection_keeper-0.1.0-py3-none-any.whl -
Subject digest:
c884b6ea2e2c6474214334728e88e372e03fe83c00b329f0ee3e65bbc77d7dda - Sigstore transparency entry: 1688046340
- Sigstore integration time:
-
Permalink:
lfstokols/claude-connection-keeper@e3139f92ef7e9f94a0a806d0c7a6641ac249ea0e -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/lfstokols
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e3139f92ef7e9f94a0a806d0c7a6641ac249ea0e -
Trigger Event:
push
-
Statement type: