From 47ba2de5577ed308c902098e55976bdfb5fc94a2 Mon Sep 17 00:00:00 2001 From: Samuel K Date: Mon, 27 Jul 2026 14:51:44 -0500 Subject: [PATCH 1/2] fix(e2e): give agent-socket cleanup polls enough budget to observe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "socket directory is cleaned up after connection close" spec (and the no-forward variant) polled a fresh `devsy ssh` connection under a 5s per-attempt context. A cold connection's tunnel + container-exec setup routinely approaches ~4s locally and exceeds 5s under CI load, so the poll process was killed mid-connect and DevsySSH returned an empty string. The Eventually discarded the error and matched on empty output, so it never observed GONE and timed out at ~30s — even though the socket had already been cleaned up promptly (~3s, verified locally against Docker). Raise the per-poll context to 30s (outer window 90s) so a cold connection can complete, and surface the poll error instead of discarding it so a genuine leak or persistent connection failure fails loudly rather than timing out on empty output. --- e2e/tests/ssh/agent_forward.go | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/e2e/tests/ssh/agent_forward.go b/e2e/tests/ssh/agent_forward.go index 214773bfc..ab64109a4 100644 --- a/e2e/tests/ssh/agent_forward.go +++ b/e2e/tests/ssh/agent_forward.go @@ -233,21 +233,23 @@ var _ = ginkgo.Describe( closeCM() closed = true - // The cleanup hop traverses the devsy proxy → in-container - // SSH server's ctx.Done(), which can take several seconds - // under CI load. Each devsy ssh observation runs on a fresh - // connection so the just-closed socket's filesystem state is - // always up-to-date. - gomega.Eventually(func() string { - pollCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + // Each observation runs on a fresh devsy ssh connection whose + // cold setup (tunnel + container exec) can take well over 5s + // under CI load. The per-poll budget must comfortably exceed + // that or the poll process is killed mid-connect and returns + // empty, which is indistinguishable from "not yet cleaned up". + // The error is surfaced (not discarded) so a genuine leak or a + // persistent connection failure fails loudly instead of timing + // out on empty output. + gomega.Eventually(func() (string, error) { + pollCtx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() - out, _ := f.DevsySSH( + return f.DevsySSH( pollCtx, tempDir, "test -S "+sockPath+" && echo PRESENT || echo GONE", ) - return out - }).WithTimeout(30*time.Second).WithPolling(500*time.Millisecond). + }).WithTimeout(90*time.Second).WithPolling(3*time.Second). Should( gomega.ContainSubstring("GONE"), "socket %s must be cleaned up after connection close", @@ -314,16 +316,16 @@ var _ = ginkgo.Describe( // On a fresh devsy ssh connection, assert no devsy-ssh-agent-* // directories remain. With lazy allocation, none are ever // created; with the cleanup goroutine, any leftover is removed. - gomega.Eventually(func() string { - pollCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + gomega.Eventually(func() (string, error) { + pollCtx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() - out, _ := f.DevsySSH( + out, err := f.DevsySSH( pollCtx, tempDir, "sh -c 'ls -d \"$XDG_RUNTIME_DIR\"/devsy-ssh-agent-* 2>/dev/null | wc -l'", ) - return strings.TrimSpace(out) - }).WithTimeout(30*time.Second).WithPolling(500*time.Millisecond). + return strings.TrimSpace(out), err + }).WithTimeout(90*time.Second).WithPolling(3*time.Second). Should( gomega.Equal("0"), "no devsy-ssh-agent-* dir must remain after a no-forward connection closes", From 0b23a1647dae06826183bfb34c144bbadea333bd Mon Sep 17 00:00:00 2001 From: Samuel K Date: Mon, 27 Jul 2026 14:52:54 -0500 Subject: [PATCH 2/2] docs(e2e): trim verbose cleanup-poll comments --- e2e/tests/ssh/agent_forward.go | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/e2e/tests/ssh/agent_forward.go b/e2e/tests/ssh/agent_forward.go index ab64109a4..613e7336d 100644 --- a/e2e/tests/ssh/agent_forward.go +++ b/e2e/tests/ssh/agent_forward.go @@ -233,14 +233,8 @@ var _ = ginkgo.Describe( closeCM() closed = true - // Each observation runs on a fresh devsy ssh connection whose - // cold setup (tunnel + container exec) can take well over 5s - // under CI load. The per-poll budget must comfortably exceed - // that or the poll process is killed mid-connect and returns - // empty, which is indistinguishable from "not yet cleaned up". - // The error is surfaced (not discarded) so a genuine leak or a - // persistent connection failure fails loudly instead of timing - // out on empty output. + // Per-poll budget must exceed a cold devsy ssh connection's + // setup, or the poll is killed mid-connect and returns empty. gomega.Eventually(func() (string, error) { pollCtx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() @@ -313,9 +307,7 @@ var _ = ginkgo.Describe( exitCmd.Env = append(os.Environ(), "SSH_AUTH_SOCK="+authSock) _ = exitCmd.Run() - // On a fresh devsy ssh connection, assert no devsy-ssh-agent-* - // directories remain. With lazy allocation, none are ever - // created; with the cleanup goroutine, any leftover is removed. + // Assert no devsy-ssh-agent-* dirs remain after close. gomega.Eventually(func() (string, error) { pollCtx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel()