Skip to content

Commit a00edfd

Browse files
aledbfclaude
andcommitted
fix(lifecycle): log why the user environment probe failed
When the userEnvProbe could not run in the container, the lifecycle commands got an empty environment and nothing said why. The reference CLI (injectHeadless.ts runUserEnvProbe) also falls back to an empty environment, but writes the error first with toErrorText; Go now does the same, through a log.ToErrorText that renders TS's red text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 81c4790 commit a00edfd

4 files changed

Lines changed: 27 additions & 2 deletions

File tree

‎internal/lifecycle/probe.go‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,10 @@ func ProbedRemoteEnv(ctx context.Context, exec ContainerExecutor, logger log.Log
171171
}
172172
server := NewShellServer(ctx, exec, containerID, remoteUser)
173173
defer server.Close()
174-
env, _ := ProbeRemoteEnv(logger, server, strategy, remoteUser, sessionDataFolder)
174+
env, err := ProbeRemoteEnv(logger, server, strategy, remoteUser, sessionDataFolder)
175+
if err != nil {
176+
logger.Write(log.ToErrorText(err.Error()), log.LevelInfo)
177+
}
175178
if env == nil {
176179
env = map[string]string{}
177180
}

‎internal/lifecycle/shell_test.go‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package lifecycle
22

33
import (
4+
"bytes"
45
"context"
56
"errors"
7+
"strings"
68
"testing"
79

810
"github.com/devcontainers/cli/internal/log"
@@ -119,7 +121,12 @@ func TestProbedRemoteEnv(t *testing.T) {
119121
}
120122

121123
fe = &fakeExecutor{fn: func(string) (string, string, int, error) { return "", "", 1, errors.New("no shell") }}
122-
if env := ProbedRemoteEnv(t.Context(), fe, log.Null, "cid", "vscode", ProbeLoginShell, ""); env == nil || len(env) != 0 {
124+
var logged bytes.Buffer
125+
logger := log.New(log.Options{Level: log.LevelInfo, Format: "text", Writer: &logged})
126+
if env := ProbedRemoteEnv(t.Context(), fe, logger, "cid", "vscode", ProbeLoginShell, ""); env == nil || len(env) != 0 {
123127
t.Fatalf("failed probe: env = %v, want empty", env)
124128
}
129+
if !strings.Contains(logged.String(), "no shell") {
130+
t.Errorf("failed probe logged %q, want the probe's error", logged.String())
131+
}
125132
}

‎internal/log/log.go‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,14 @@ const (
284284
numberColor = "38;2;86;156;214"
285285
)
286286

287+
func ToErrorText(s string) string {
288+
lines := newlines.Split(s, -1)
289+
for i, l := range lines {
290+
lines[i] = color("31", l)
291+
}
292+
return strings.Join(lines, "\r\n") + "\r\n"
293+
}
294+
287295
func color(c, s string) string {
288296
return "\x1b[1m\x1b[" + c + "m" + s + "\x1b[39m\x1b[22m"
289297
}

‎internal/log/log_test.go‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,10 @@ func TestLogLevelFilter_TerminalVsJSON(t *testing.T) {
189189
t.Errorf("json must emit all levels, got %q", js)
190190
}
191191
}
192+
193+
func TestToErrorTextMatchesTheReference(t *testing.T) {
194+
want := "\x1b[1m\x1b[31mfirst\x1b[39m\x1b[22m\r\n\x1b[1m\x1b[31msecond\x1b[39m\x1b[22m\r\n"
195+
if got := ToErrorText("first\r\nsecond"); got != want {
196+
t.Errorf("ToErrorText = %q, want %q", got, want)
197+
}
198+
}

0 commit comments

Comments
 (0)