Skip to content

Commit 7fecd39

Browse files
authored
fix(agent/agentscripts): display informative error for ErrWaitDelay (#10407)
Fixes #10400
1 parent 99fda4a commit 7fecd39

File tree

2 files changed

+26
-2
lines changed

2 files changed

+26
-2
lines changed

agent/agent.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,7 @@ func (a *agent) run(ctx context.Context) error {
743743
return script.RunOnStart
744744
})
745745
if err != nil {
746-
a.logger.Warn(ctx, "startup script failed", slog.Error(err))
746+
a.logger.Warn(ctx, "startup script(s) failed", slog.Error(err))
747747
if errors.Is(err, agentscripts.ErrTimeout) {
748748
a.setLifecycle(ctx, codersdk.WorkspaceAgentLifecycleStartTimeout)
749749
} else {
@@ -1465,6 +1465,7 @@ func (a *agent) Close() error {
14651465
return script.RunOnStop
14661466
})
14671467
if err != nil {
1468+
a.logger.Warn(ctx, "shutdown script(s) failed", slog.Error(err))
14681469
if errors.Is(err, agentscripts.ErrTimeout) {
14691470
lifecycleState = codersdk.WorkspaceAgentLifecycleShutdownTimeout
14701471
} else {

agent/agentscripts/agentscripts.go

+24-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ import (
2727
var (
2828
// ErrTimeout is returned when a script times out.
2929
ErrTimeout = xerrors.New("script timed out")
30+
// ErrOutputPipesOpen is returned when a script exits leaving the output
31+
// pipe(s) (stdout, stderr) open. This happens because we set WaitDelay on
32+
// the command, which gives us two things:
33+
//
34+
// 1. The ability to ensure that a script exits (this is important for e.g.
35+
// blocking login, and avoiding doing so indefinitely)
36+
// 2. Improved command cancellation on timeout
37+
ErrOutputPipesOpen = xerrors.New("script exited without closing output pipes")
3038

3139
parser = cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.DowOptional)
3240
)
@@ -248,7 +256,22 @@ func (r *Runner) run(ctx context.Context, script codersdk.WorkspaceAgentScript)
248256
err = cmdCtx.Err()
249257
case err = <-cmdDone:
250258
}
251-
if errors.Is(err, context.DeadlineExceeded) {
259+
switch {
260+
case errors.Is(err, exec.ErrWaitDelay):
261+
err = ErrOutputPipesOpen
262+
message := fmt.Sprintf("script exited successfully, but output pipes were not closed after %s", cmd.WaitDelay)
263+
details := fmt.Sprint(
264+
"This usually means a child process was started with references to stdout or stderr. As a result, this " +
265+
"process may now have been terminated. Consider redirecting the output or using a separate " +
266+
"\"coder_script\" for the process, see " +
267+
"https://coder.com/docs/v2/latest/templates/troubleshooting#startup-script-issues for more information.",
268+
)
269+
// Inform the user by propagating the message via log writers.
270+
_, _ = fmt.Fprintf(cmd.Stderr, "WARNING: %s. %s\n", message, details)
271+
// Also log to agent logs for ease of debugging.
272+
r.Logger.Warn(ctx, message, slog.F("details", details), slog.Error(err))
273+
274+
case errors.Is(err, context.DeadlineExceeded):
252275
err = ErrTimeout
253276
}
254277
return err

0 commit comments

Comments
 (0)