cd /news/developer-tools/show-hn-cckeep-claude-code-s-remote-… · home topics developer-tools article
[ARTICLE · art-85661] src=github.com ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Show HN: Cckeep – Claude Code's remote session gives up reconnecting in 31s

Cckeep, a new open-source tool by developer Kamihork, prevents Claude Code's Remote Control sessions from silently disconnecting by re-arming them within 31 seconds, addressing a known issue where sessions give up reconnecting after 5 attempts with exponential backoff. The tool runs as a background job via launchd on macOS or systemd on Linux, checking every 15 seconds, and requires Claude Code to run inside tmux. It also works around a separate bug (anthropics/claude-code#34255) where sessions wedge in reconnecting mode, which has been open since March 2026 with 99 upvotes and no fix.

read11 min views2 publishedAug 4, 2026
Show HN: Cckeep – Claude Code's remote session gives up reconnecting in 31s
Image: source

Keeps Claude Code Remote Control from silently going dead.

Remote Control retries for about 31 seconds and then gives up for good.

cckeep

notices, and re-arms the session — without touching one that's busy.

Remote Control lets you drive a local Claude Code session from your phone or from claude.ai. It reconnects on its own when the link drops — for 5 attempts with 1/2/4/8/16-second backoff. That is a 31-second budget. Close your laptop lid, switch Wi-Fi, ride an elevator, and the budget is gone. The connection closes and never comes back.

There is a second failure too: the session wedges in /rc reconnecting

and sits there forever. That one is anthropics/claude-code#34255 — open since March 2026, 99 👍, no fix.

Either way you find out the same way: you reach for your phone, and the session is gone. The documented recovery is to walk back to your desk and type /remote-control

.

npm install -g cckeep
cckeep enable

npm install

only puts the CLI on your PATH

; cckeep enable

is the step that registers a background job — launchd on macOS, a systemd user timer on Linux — that checks every 15 seconds and re-arms whatever went dead.

Install it globally rather than running npx cckeep enable

. The scheduled job runs cckeep from wherever it was installed, and npx's cache is throwaway: a job pointing into it keeps working until the cache is cleared and then stops, silently — the one failure a watchdog must not have. cckeep enable

refuses to schedule from an npx path for that reason.

To look before installing anything, npx

is fine — neither of these changes a thing:

npx cckeep            # what it sees right now
npx cckeep doctor     # check tmux, panes, and the scheduler

** command not found: cckeep right after installing?** You are on a shim-based version manager.

nodenv

and rbenv

-style setups need a rehash before a newly installed binary appears on PATH

, and nvm

needs a new shell:

nodenv rehash     # nodenv
asdf reshim nodejs # asdf

One requirement: Claude Code has to be running inside tmux. A session started in a bare terminal cannot be reached from another process, so there is nothing any tool can do for it. See Running Claude Code in tmux.

If cckeep saved you a walk back to your desk, a ⭐ helps other Remote Control users find it.

This is the whole design problem. A watchdog that types into your terminal on a timer is a liability unless it is certain the moment is safe. Every one of these is enforced, and tested:

Never on top of what you typed. Enter submits whatever is in the composer, so an unsent draft would go out with the command glued onto it. If anything is in the box — or the box cannot be found at all — nothing is sent. The idle check cannot cover this: a draft sitting in the box is perfectly still.Never during a turn. The pane is captured three times. A running turn animates a spinner and a token counter, so identical captures mean nothing is happening. Three rather than two, at an interval that is deliberately not round, because any animation whose period divides the interval would otherwise alias into identical frames.Never into a dialog. Permission prompts turn Enter into a selection. A selection marker counts anywhere on screen; the plain English phrasings only count near the composer, since Claude Code writes sentences like "Do you want me to run the tests as well?" in ordinary replies.Never into the panel you opened./remote-control

opens a status panel with a QR code. cckeep only presses Enter there when it opened the panel itself, and only if the panel is still up when it goes to press it.Never a session that only mentioned it. The indicators are read from the last few rows, never from the transcript, so a session discussing/rc active

is not mistaken for a connected one. A pane that has never been seen connected is only ever acted on when Claude Code itself prints that the link died.Never in a tight loop, and never twice at once. One action per pane per 5 minutes, and a lock so thatcckeep watch

running alongside the scheduled job cannot interleave two passes into one garbled prompt.Never against a wall. Replies like “Remote Control requires a claude.ai subscription” mean reconnecting is impossible here — an auth or plan problem the command cannot fix — so the pane is dropped until the link is seen healthy again. And for failure modes it cannot recognise, at most 3 re-arms per outage; after that it stops and waits to see the link healthy rather than retyping into your transcript forever.Re-checked at the last moment. The decision is made from one capture, then re-verified after the wait — reconnected in between, dialog appeared, something typed? Nothing is sent, and the pane keeps the progress it had made rather than starting its wait over.

--dry-run

prints what it would do and sends nothing.

State on screen What it means What cckeep does
/rc active , or a truncated /rc
connected remembers the pane, nothing else
/rc reconnecting
inside the 31-second budget waits — this usually resolves
/rc reconnecting , 2 minutes on
wedged (

Remote Control disconnected

cckeep                 # status: one line per Claude Code pane
cckeep watch           # run in the foreground instead of scheduling
cckeep once            # a single pass — what the scheduler runs
cckeep enable          # start checking in the background
cckeep disable         # stop checking
cckeep doctor          # tmux, panes, scheduler, paths
cckeep logs            # what it has done

Options: --dry-run

, --json

, --interval <s>

, --lang en|ja

(auto-detected from LANG

).

install

and uninstall

still work as aliases for enable

and disable

.

cckeep reads and types into tmux panes. That is the only channel a separate process has into a live Claude Code session — and it is why the session must be started inside tmux. Restarting the process is not an alternative: it would end the conversation, which is exactly what you are trying to save.

The smallest change is a shell function that wraps interactive launches only, so claude update

, claude doctor

and claude -p

still behave normally:

cc() {
  local a
  for a in "$@"; do
    case "$a" in
      -p|--print|-v|--version|-h|--help|--bg|--background|--output-format) command claude "$@"; return ;;
      agents|auth|doctor|install|mcp|plugin|project|setup-token|update|upgrade|remote-control|rc|config)
        command claude "$@"; return ;;
      -*) ;;
      *) break ;;
    esac
  done
  local session="claude-$(basename "$PWD")-$(printf '%s' "$PWD" | cksum | cut -d' ' -f1)"
  if [ -n "$TMUX" ]; then command claude "$@"; return; fi
  if ! tmux has-session -t "=$session" 2>/dev/null; then
    tmux new-session -s "$session" -c "$PWD" claude "$@"; return
  fi

  local pane_cmd cmd
  pane_cmd=$(tmux list-panes -t "=$session:" -F '#{pane_current_command}' 2>/dev/null | head -1)
  case "$pane_cmd" in
    zsh|bash|sh|fish)
      cmd="claude"
      case " $* " in
        *" -c "*|*" --continue "*|*" -r "*|*" --resume "*|*" --session-id "*) ;;
        *) cmd="$cmd --continue" ;;
      esac
      [ $# -gt 0 ] && cmd="$cmd $*"
      tmux send-keys -t "=$session:" "$cmd" C-m ;;
  esac
  tmux attach-session -t "=$session"
}

cc -c

and cc --continue

work as you'd expect — the flag is passed straight through to Claude Code. The block above covers the case the flag can't: a tmux session whose Claude Code process has already exited.

Claude Code also needs two lines in ~/.tmux.conf

, or Shift+Enter and desktop notifications break inside tmux (official guidance):

set -g allow-passthrough on
set -s extended-keys on
set -as terminal-features 'xterm*:extkeys'

Ctrl+B needs no fix: Claude Code detects tmux and rebinds its own shortcut to Ctrl+B Ctrl+B

.

Defaults are tuned so you never notice it. Override in ~/.cckeep/config.json

, by environment variable, or per-run flag — later wins. A malformed config is a hard error rather than a silent half-load.

{
  "interval": 15,
  "cooldown": 300,
  "stuckLimit": 8,
  "missLimit": 4,
  "settle": 2000,
  "paneCommand": "claude"
}

interval

— seconds between passes (also whatenable

schedules)cooldown

— seconds before the same pane may be acted on againstuckLimit

— checks inreconnecting

before the bridge is treated as wedgedmissLimit

— checks with no indicator before re-arming a pane that had onemaxRearms

— re-arms allowed per outage before it gives up until the link is seen healthy againsettle

— milliseconds between the two captures of the idle check; raise it on a slow machinepaneCommand

— foreground process name that marks a pane as Claude CodetmuxSocket

— socket name or path, if your tmux runs on something other than the default server (tmux -L name

/-S path

). Empty means the defaulttmuxBinary

— absolute path to tmux, if yours lives somewhere the usual lookup misses

Every key has an env twin: CCKEEP_INTERVAL

, CCKEEP_COOLDOWN

, CCKEEP_STUCK_LIMIT

, CCKEEP_MISS_LIMIT

, CCKEEP_MAX_REARMS

, CCKEEP_SETTLE

, CCKEEP_PANE_COMMAND

, CCKEEP_TMUX_SOCKET

, CCKEEP_TMUX

. Whatever is set when you run cckeep enable

is written into the scheduled job, so a socket set only in your shell does not quietly go missing from the background run. CCKEEP_HOME

moves state, config and log off ~/.cckeep

.

cckeep re-arms the connection. It does not raise Claude Code's retry budget — that is a constant inside a closed-source binary, and only Anthropic can change it. If #34255 is fixed, this tool becomes unnecessary, which is the right outcome. Until then, a 👍 there is worth more than a star here.

Out of reach by design:

Sessions outside tmux— no channel to type into** The VS Code extension**— not a terminal TUI; tmux cannot wrap it** Server mode**(claude remote-control

) — that one is a process you own, so supervise it with launchd/systemd directly, or awhile true

loopOutages past ~10 minutes— Claude Code exits the session itself; there is nothing left to re-arm

cckeep reads the visible text of your tmux panes to decide whether a pane is connected. That text is your conversation. Therefore:

  • everything stays on your machine; there is no network code in this package
  • no telemetry, no account, no phone-home
  • pane text is matched against a handful of indicator strings and thrown away — only pane labels and verdicts reach the log
  • the log lives at ~/.cckeep/cckeep.log

and rolls over at 512 KB, keeping one previous generation;cckeep logs

prints the recent lines

Claude Code paints a Remote Control indicator in its footer: /rc active

when connected, /rc reconnecting

while retrying, and a Remote Control disconnected

notification when it gives up. cckeep finds the panes running Claude Code, reads those indicators out of tmux capture-pane

, and keeps a small per-pane counter in ~/.cckeep/state.json

.

Finding those panes takes one extra step: Claude Code rewrites its own process title, so tmux reports such a pane as 2.1.220

rather than claude

. Matching the name tmux reports therefore finds nothing on a real machine. cckeep checks the process table as well, and treats a pane as Claude Code's when the pane's process — or anything it spawned — is actually claude

.

The indicator is right-aligned, so a custom status line or a narrow pane squeezes it down to a bare /rc

with the word cut off. cckeep treats that as connected: the indicator only renders while a link exists, and reading it as connected merely records the pane and waits.

Where it looks matters as much as what it looks for. The state indicators are read from the last dozen lines only, because the words themselves turn up in ordinary conversation — a session where you happen to discuss /rc active

would otherwise read as connected. Dialog and status-panel detection deliberately scans the whole pane instead: a false positive there costs one skipped pass, while a miss costs a keystroke in the wrong place.

The decision layer (src/detect.js

) is a pure function of screen text plus prior state, which is why the safety rules can be tested exhaustively without a terminal. The runner (src/run.js

) does the I/O: the idle check, the last-moment re-check, and the keystrokes.

None of this is a published API — the indicator strings are UI text and can change. When they do, cckeep stops acting rather than acting wrongly: a pane it cannot read looks "never connected", and panes it has never seen connected are never touched.

git clone https://github.com/kamihork/cckeep.git && cd cckeep
npm test                       # 36 tests, no network, no tmux required
node bin/cckeep.js doctor

The test suite fakes tmux, so it runs anywhere. Contributions welcome — especially indicator strings from Claude Code versions or terminals where detection misses. See CONTRIBUTING.md.

── more in #developer-tools 4 stories · sorted by recency
── more on @cckeep 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/show-hn-cckeep-claud…] indexed:0 read:11min 2026-08-04 ·