Skip to content

[Bug]: workspace ssh logs spurious ERROR "keepalive request rejected" every ~55s #1210

Description

@yosimasu

Before submitting

  • I have searched existing issues to confirm this is not a duplicate
  • I am running the latest version of Devsy

What happened?

After devsy workspace ssh connects to a workspace, the CLI repeatedly logs an ERROR every --ssh-keepalive-interval (default 55s), for the entire lifetime of the session:

2026-09-09T11:06:10.503+0800    ERROR    failed to send keepalive: keepalive request rejected

Root cause: startSSHKeepAlive in cmd/workspace/ssh.go sends a keepalive@openssh.com global request over the container-side SSH connection on a ticker:

func startSSHKeepAlive(ctx context.Context, client *ssh.Client, interval time.Duration) {
	ticker := time.NewTicker(interval)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			ok, _, err := client.SendRequest("keepalive@openssh.com", true, nil)
			if err := checkKeepAliveResponse(ok, err); err != nil {
				log.Errorf("failed to send keepalive: %v", err)
			}
		}
	}
}

But the container-side devsy internal ssh-server (pkg/ssh/server/ssh.go) only registers handlers for port-forwarding global requests:

RequestHandlers: map[string]ssh.RequestHandler{
    "tcpip-forward":                          forwardHandler.HandleSSHRequest,
    "streamlocal-forward@openssh.com":        forwardedUnixHandler.HandleSSHRequest,
    "cancel-streamlocal-forward@openssh.com": forwardedUnixHandler.HandleSSHRequest,
    "cancel-tcpip-forward":                   forwardHandler.HandleSSHRequest,
},

There is no handler for keepalive@openssh.com, so the underlying gliderlabs/ssh server rejects it by default (ok=false), which checkKeepAliveResponse turns into errors.New("keepalive request rejected"). This is logged at ERROR level, which reads as a real failure, even though nothing is actually broken.

Note the server already enforces its own independent liveness check (same file):

keepAliveInterval, keepAliveCountMax := keepAliveConfig() // 15s / 8
ClientAliveInterval: keepAliveInterval,
ClientAliveCountMax: keepAliveCountMax,

So the client-side keepalive@openssh.com probe is redundant with the server's own ClientAliveInterval/ClientAliveCountMax, and is guaranteed to fail against the current server implementation on every run — it's not transient/network flakiness.

What did you expect to happen instead?

No ERROR-level log noise for a condition that doesn't indicate any actual problem with the session (the SSH tunnel, nx serve bff, and all in-container connectivity keep working fine throughout).

Steps to reproduce

  1. devsy workspace up . a docker-provider workspace (e.g. any devcontainer-based repo).
  2. devsy workspace ssh <workspace> and leave the session open for more than 55s.
  3. Observe failed to send keepalive: keepalive request rejected logged every --ssh-keepalive-interval (default 55s) for the remainder of the session, with no other symptoms.

devcontainer.json

{
  "name": "talent-bff",
  "dockerComposeFile": "compose.yaml",
  "service": "bff",
  "workspaceFolder": "/workspaces/talent-bff",
  "remoteUser": "vscode"
}

Error output / logs

2026-09-09T11:06:10.503+0800    ERROR    failed to send keepalive: keepalive request rejected

(repeats every ~55s for the life of the devsy workspace ssh session)

How often does this happen?

Every time

Operating system

macOS

Architecture

ARM64

Desktop app or CLI?

CLI only

Devsy version

v1.17.0

Devsy provider

Docker

Provider version

v1.0.0

Screenshots

N/A — not a UI bug.

Anything else?

  • Confirmed the in-container devsy agent binary is also v1.17.0 (matches the CLI), ruling out a version-skew explanation between host CLI and injected container agent.
  • Docker backend is Colima (DOCKER_HOST=unix:///Users/masu.lin/.colima/default/docker.sock); checked Colima/Lima VM logs around the error timestamp and found nothing — the VM and docker socket are healthy, so this isn't a Colima-level connectivity issue either.

Suggested fixes (any one of these would resolve it):

  • Register a handler for keepalive@openssh.com in pkg/ssh/server/ssh.go's RequestHandlers that just replies true (a no-op ack), matching real OpenSSH server behavior.
  • Drop the redundant client-side keepalive in cmd/workspace/ssh.go entirely, since the server already enforces liveness via ClientAliveInterval/ClientAliveCountMax.
  • At minimum, log the rejection at Debug level instead of Error, since it's an expected/harmless outcome against the current server implementation, not an actionable failure.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions