Skip to content

Close cockpit mcp's tunnel after 90 s idle instead of holding it all session - #113

Merged
addisdev merged 1 commit into
mainfrom
cockpit-mcp-idle-tunnel
Oct 22, 2026
Merged

addisdev merged 1 commit into
mainfrom
cockpit-mcp-idle-tunnel

Conversation

@addisdev

Copy link
Copy Markdown
Owner

Why

Each cockpit mcp process (one per open Claude Code session) held ssh … -L 127.0.0.1:<port>:127.0.0.1:7878 ultra cat >/dev/null open for the whole session. Each of those used one of ultra's 42 sshd slots (launchd's sealed Instances cap). On 2026-10-22 idle sessions filled 40 of them, and every proxied MCP server failed to connect.

What

  • OnDemandRoute (CockpitCore, replaces the CLI's RouteCache) opens the transport the first time a call needs it. With idle set, it closes the transport once no call has been in flight for that long.
    • Concurrent calls share one open, so they no longer race TunnelTransport.open(), which closes whatever was already there.
    • A call in flight, such as a wait_for_checks held for minutes, keeps the tunnel up.
    • A dead tunnel is reopened on the next call.
    • The idle close runs under the lock, so it can't kill a tunnel that a newer call just opened.
  • cockpit mcp uses it with a 90 s idle window by default. --idle <s> changes the window, and --idle 0 holds the tunnel for the whole session as before.
  • cockpit wait uses the same type without an idle close, so its behaviour is unchanged.
  • cockpit sentinel is untouched. It still probes out of band.
  • Docs: docs/cockpit/agents.md and docs/cockpit/cli.md.

Tests

TransportTests.swift adds 9 tests:

  • 8 against a counting transport and the stub daemon:
    • the tunnel opens on first use, not before
    • it closes after the idle window, and the next call reopens it
    • an in-flight call keeps it open past the window
    • a new call cancels a pending close
    • 5 concurrent calls share one open
    • a dead tunnel is reopened
    • invalidate drops the route
    • with no idle window, the route is held
  • 1 end to end: the real TunnelTransport drives a fake ssh (a python3 forwarder that exits on stdin EOF, like the remote cat). The test checks that the process is gone after the idle window and that a new one answers on the next call.

The full suite passes locally:

  • swift test: 115 tests
  • CLI smoke
  • check-docs.sh
  • dashboard npm test: 1078 pass, 1 skipped (postgres, no TEST_DATABASE_URL locally)
  • both --check scripts
  • all 11 shell tests

Live check against ultra

Three cockpit mcp processes ran from this build, with initialize and tools/list sent to each:

time ssh children cat sessions on ultra
before — 13
tunnels up 1, 1, 1 16
idle 60 s 1, 1, 1 16
idle 120 s 0, 0, 0 13

fleet_status after the idle spell answered in 0.3 s on each, through a fresh tunnel.

The 13 baseline sessions are the installed (old) cockpit mcp processes and the app. They go away once this is merged and the CLI is reinstalled to ~/.local/lib/fleet-cockpit, and sessions restart their MCP servers.

🤖 Generated with Claude Code

…session

Each `cockpit mcp` (one per open Claude Code session) held an ssh tunnel to
ultra for the session's whole life, each one of the 42 sshd slots launchd
allows; on 2026-10-22 idle sessions filled 40 and proxied MCP servers failed
to connect. The tunnel now opens when a message needs the daemon and closes
after --idle seconds (default 90, 0 = hold) with no call in flight; a long
wait_for_checks keeps it open until it returns. Concurrent calls share one
open. RouteCache moves into CockpitCore as OnDemandRoute, which `cockpit wait`
also uses (without the idle close).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@addisdev
addisdev merged commit 9b19df8 into main Oct 22, 2026
21 of 22 checks passed
@addisdev
addisdev deleted the cockpit-mcp-idle-tunnel branch October 22, 2026 19:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant