From d351821ec3148870771e9352dc6115af0a29eb8e Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 8 Dec 2025 16:11:47 +0200 Subject: [PATCH] fix(cli/cliui): skip startup script logs when Wait=false (#21105) When users pass --wait=no or set CODER_SSH_WAIT=no, startup logs are no longer dumped to stderr. The stage indicator is still shown, just not the log content. Fixes #13580 --- cli/cliui/agent.go | 26 ++++++++----- cli/cliui/agent_test.go | 81 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 10 deletions(-) diff --git a/cli/cliui/agent.go b/cli/cliui/agent.go index 111c6b0a6f..e09c440a06 100644 --- a/cli/cliui/agent.go +++ b/cli/cliui/agent.go @@ -208,19 +208,25 @@ func (aw *agentWaiter) handleConnected(ctx context.Context, agent codersdk.Works aw.sw.Log(time.Time{}, codersdk.LogLevelInfo, "==> ℹ︎ To connect immediately, reconnect with --wait=no or CODER_SSH_WAIT=no, see --help for more information.") } - agent, err := aw.streamLogs(ctx, agent, follow, fetchedAgent) - if err != nil { - return err - } - - // If we were following, wait until startup completes. - if follow { - agent, err = aw.pollWhile(ctx, agent, func(agent codersdk.WorkspaceAgent) bool { - return agent.LifecycleState.Starting() - }) + // In non-blocking mode (Wait=false), we don't stream logs. This prevents + // dumping a wall of logs on users who explicitly pass --wait=no. The stage + // indicator is still shown, just not the log content. See issue #13580. + if aw.opts.Wait { + var err error + agent, err = aw.streamLogs(ctx, agent, follow, fetchedAgent) if err != nil { return err } + + // If we were following, wait until startup completes. + if follow { + agent, err = aw.pollWhile(ctx, agent, func(agent codersdk.WorkspaceAgent) bool { + return agent.LifecycleState.Starting() + }) + if err != nil { + return err + } + } } // Handle final lifecycle state. diff --git a/cli/cliui/agent_test.go b/cli/cliui/agent_test.go index 7e5ea692f7..ea5f855d0e 100644 --- a/cli/cliui/agent_test.go +++ b/cli/cliui/agent_test.go @@ -268,6 +268,87 @@ func TestAgent(t *testing.T) { "For more information and troubleshooting, see", }, }, + { + // Verify that in non-blocking mode (Wait=false), startup script + // logs are suppressed. This prevents dumping a wall of logs on + // users who explicitly pass --wait=no. See issue #13580. + name: "No logs in non-blocking mode", + opts: cliui.AgentOptions{ + FetchInterval: time.Millisecond, + Wait: false, + }, + iter: []func(context.Context, *testing.T, *codersdk.WorkspaceAgent, <-chan string, chan []codersdk.WorkspaceAgentLog) error{ + func(_ context.Context, _ *testing.T, agent *codersdk.WorkspaceAgent, _ <-chan string, logs chan []codersdk.WorkspaceAgentLog) error { + agent.Status = codersdk.WorkspaceAgentConnected + agent.FirstConnectedAt = ptr.Ref(time.Now()) + agent.StartedAt = ptr.Ref(time.Now()) + agent.LifecycleState = codersdk.WorkspaceAgentLifecycleStartError + agent.ReadyAt = ptr.Ref(time.Now()) + // These logs should NOT be shown in non-blocking mode. + logs <- []codersdk.WorkspaceAgentLog{ + { + CreatedAt: time.Now(), + Output: "Startup script log 1", + }, + { + CreatedAt: time.Now(), + Output: "Startup script log 2", + }, + } + return nil + }, + }, + // Note: Log content like "Startup script log 1" should NOT appear here. + want: []string{ + "⧗ Running workspace agent startup scripts (non-blocking)", + "✘ Running workspace agent startup scripts (non-blocking)", + "Warning: A startup script exited with an error and your workspace may be incomplete.", + "For more information and troubleshooting, see", + }, + }, + { + // Verify that even after waiting for the agent to connect, logs + // are still suppressed in non-blocking mode. See issue #13580. + name: "No logs after connection wait in non-blocking mode", + opts: cliui.AgentOptions{ + FetchInterval: time.Millisecond, + Wait: false, + }, + iter: []func(context.Context, *testing.T, *codersdk.WorkspaceAgent, <-chan string, chan []codersdk.WorkspaceAgentLog) error{ + func(_ context.Context, _ *testing.T, agent *codersdk.WorkspaceAgent, _ <-chan string, _ chan []codersdk.WorkspaceAgentLog) error { + agent.Status = codersdk.WorkspaceAgentConnecting + return nil + }, + func(_ context.Context, t *testing.T, agent *codersdk.WorkspaceAgent, output <-chan string, _ chan []codersdk.WorkspaceAgentLog) error { + return waitLines(t, output, "⧗ Waiting for the workspace agent to connect") + }, + func(_ context.Context, _ *testing.T, agent *codersdk.WorkspaceAgent, _ <-chan string, logs chan []codersdk.WorkspaceAgentLog) error { + agent.Status = codersdk.WorkspaceAgentConnected + agent.FirstConnectedAt = ptr.Ref(time.Now()) + agent.StartedAt = ptr.Ref(time.Now()) + agent.LifecycleState = codersdk.WorkspaceAgentLifecycleStartError + agent.ReadyAt = ptr.Ref(time.Now()) + // These logs should NOT be shown in non-blocking mode, + // even though we waited for connection. + logs <- []codersdk.WorkspaceAgentLog{ + { + CreatedAt: time.Now(), + Output: "Startup script log 1", + }, + } + return nil + }, + }, + // Note: Log content should NOT appear here despite waiting for connection. + want: []string{ + "⧗ Waiting for the workspace agent to connect", + "✔ Waiting for the workspace agent to connect", + "⧗ Running workspace agent startup scripts (non-blocking)", + "✘ Running workspace agent startup scripts (non-blocking)", + "Warning: A startup script exited with an error and your workspace may be incomplete.", + "For more information and troubleshooting, see", + }, + }, { name: "Error when shutting down", opts: cliui.AgentOptions{