Skip to main content

Detect and revive Claude Code remote-control connections

Project description

claude-connection-keeper

CI PyPI Python versions License: MIT

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 connected verdict is high-confidence. A disconnected verdict is medium-confidence — strong, but treat it as "probably disconnected," not "certainly."
  • Anything the tool can't read cleanly becomes unknown and 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 $TMUX set; if cck can't see panes from cron, run the job from a tmux-aware login shell or point it at the socket with tmux -S.
  • The heartbeat is one line every 4 min (~360/day) — add a logrotate rule 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 --reconnectable already 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-S stash any prompt text you'd left half-typed, so we don't clobber it
    2 literal /remote-control the reconnect command, sent as literal text
    3 Enter submit
    ~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

  1. Discover — read per-pid JSON files from ~/.claude/sessions/ (honors CLAUDE_CONFIG_DIR), keeping only sessions whose pid is alive.
  2. ClassifybridgeSessionId present → connected; absent/empty but a known status is present → disconnected; otherwise → unknown.
  3. Map to a pane — the session file is keyed on the claude pid, but the tmux pane_pid is 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.
  4. Reconnect (opt-in) — tmux send-keys the 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 claude was 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.
  • disconnected is 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

claude_connection_keeper-0.1.0.tar.gz (19.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

claude_connection_keeper-0.1.0-py3-none-any.whl (15.7 kB view details)

Uploaded Python 3

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

Hashes for claude_connection_keeper-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f369ac2757cb4ddaf983ce2d33abf1120319e7447e11749f1df64f56de09e257
MD5 f2bc9301ab3d0e59eac7e24f37b27309
BLAKE2b-256 1a3aab40ecd08a025a267c51183fd5c1eec273d009d27fd653fabe8a1c6ca090

See more details on using hashes here.

Provenance

The following attestation bundles were made for claude_connection_keeper-0.1.0.tar.gz:

Publisher: release.yml on lfstokols/claude-connection-keeper

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file claude_connection_keeper-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for claude_connection_keeper-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c884b6ea2e2c6474214334728e88e372e03fe83c00b329f0ee3e65bbc77d7dda
MD5 9969d3584d5e966f3db4d712236ae4b4
BLAKE2b-256 21b36c863d68c19dffc4d30fbebef8695214468697eb42a0ddcfdcb20a37998b

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page