From 5ebf72aa0da6517e993cdb9f9056266a37ee2753 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 14 Apr 2026 20:03:12 +0200 Subject: [PATCH 1/7] Improve privileged mode error message with actionable suggestions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace cryptic 'unlazy force execution: failed to load LLB: security.insecure is not allowed' error with clear, user-friendly message. Changes: - cmd/earthly/app/run.go: Create clear error message with 3 solution options * Run with -P flag * Set EARTHLY_ALLOW_PRIVILEGED environment variable * Add to config file * Technical details only shown with -V or --debug flags - logbus/formatter/formatter.go: Print help message for build failures - tests/with-docker/Earthfile: Add simple test target for privileged tests - tests/Earthfile: Add comprehensive tests for all privileged operations * RUN --privileged * WITH DOCKER * IF --privileged * FOR --privileged Before: Error: build target: build main: failed to solve: unlazy force execution: failed to load LLB: security.insecure is not allowed Help: earth --allow-privileged (earth -P) flag is required After: Error: This build uses privileged operations which requires privileged mode. Help: To fix this, use one of the following: • Run with the -P flag: earth -P +your-target • Set environment variable: export EARTHLY_ALLOW_PRIVILEGED=true • Add to config: earth config global.allow_privileged true Clean up error handling: remove duplicate HelpPrint calls and simplify verbose warning --- cmd/earthly/app/run.go | 29 ++++++++++------ logbus/formatter/formatter.go | 4 +++ tests/Earthfile | 63 +++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 10 deletions(-) diff --git a/cmd/earthly/app/run.go b/cmd/earthly/app/run.go index e8cb4c0590..27e9823ad7 100644 --- a/cmd/earthly/app/run.go +++ b/cmd/earthly/app/run.go @@ -188,7 +188,6 @@ func (app *EarthlyApp) handleError(ctx context.Context, err error, args []string hintErr.Hint(), hintErr.Message(), ) - app.BaseCLI.Console().HelpPrint(hintErr.Hint()) return 1 case errors.As(err, &autoSkipErr): @@ -218,7 +217,6 @@ func (app *EarthlyApp) handleError(ctx context.Context, err error, args []string helpMsg = "Are you using --platform to target a different architecture? You may have to manually install QEMU.\n" + "For more information see https://docs.earthbuild.dev/guides/multi-platform\n" - app.BaseCLI.Console().HelpPrint(helpMsg) app.BaseCLI.Logbus().Run().SetGenericFatalError( time.Now(), logstream.FailureType_FAILURE_TYPE_OTHER, @@ -236,7 +234,6 @@ func (app *EarthlyApp) handleError(ctx context.Context, err error, args []string args = stringutil.FilterElementsFromList(args, "--ci") msg := "To debug your build, you can use the --interactive (-i) flag to drop into a shell of the failing RUN step" helpMsg = fmt.Sprintf("%s: %q\n", msg, strings.Join(args, " ")) - app.BaseCLI.Console().HelpPrint(helpMsg) } // This error would have been displayed earlier from the SolverMonitor. // This SetGenericFatalError is a catch-all just in case that hasn't happened. @@ -249,14 +246,30 @@ func (app *EarthlyApp) handleError(ctx context.Context, err error, args []string return 1 case strings.Contains(err.Error(), "security.insecure is not allowed"): - helpMsg := "earth --allow-privileged (earth -P) flag is required\n" + // Extract target info from error if available + targetInfo := "" + if ie != nil && isInterpreterError { + targetInfo = ie.TargetID + } + + userMsg := "This build requires privileged mode." + if targetInfo != "" { + userMsg = fmt.Sprintf("Target %s requires privileged mode.", targetInfo) + } + + helpMsg := "To fix this, use one of the following:\n" + + " • Run with the -P flag: earth -P +your-target\n" + + " • Set environment variable: export EARTHLY_ALLOW_PRIVILEGED=true\n" + + " • Add to config: earth config global.allow_privileged true" + app.BaseCLI.Logbus().Run().SetGenericFatalError( time.Now(), logstream.FailureType_FAILURE_TYPE_NEEDS_PRIVILEGED, helpMsg, - err.Error(), + userMsg, ) - app.BaseCLI.Console().HelpPrint(helpMsg) + + app.BaseCLI.Console().VerboseWarnf("Technical details: %v\n", err.Error()) return 9 case strings.Contains(err.Error(), errutil.EarthlyGitStdErrMagicString): @@ -277,8 +290,6 @@ func (app *EarthlyApp) handleError(ctx context.Context, err error, args []string app.BaseCLI.Console().VerboseWarnf("Error: %v\n", err.Error()) } - app.BaseCLI.Console().HelpPrint(helpMsg) - return 1 case strings.Contains(err.Error(), "failed to compute cache key") && strings.Contains(err.Error(), ": not found"): matches := notFoundRegex.FindStringSubmatch(err.Error()) @@ -310,7 +321,6 @@ func (app *EarthlyApp) handleError(ctx context.Context, err error, args []string helpMsg := fmt.Sprintf("%s responded with a rate limit error. This is usually because you are not logged in.\n"+ "You can login using the command:\n"+ " docker login%s", registryName, registryHost) - app.BaseCLI.Console().HelpPrint(helpMsg) app.BaseCLI.Logbus().Run().SetGenericFatalError( time.Now(), logstream.FailureType_FAILURE_TYPE_RATE_LIMITED, @@ -338,7 +348,6 @@ func (app *EarthlyApp) handleError(ctx context.Context, err error, args []string "Verify your account to lift this restriction." app.BaseCLI.Logbus().Run(). SetGenericFatalError(time.Now(), logstream.FailureType_FAILURE_TYPE_OTHER, helpMsg, grpcErr.Message()) - app.BaseCLI.Console().HelpPrint(helpMsg) return 1 case grpcErrOK && grpcErr.Code() != codes.Canceled: diff --git a/logbus/formatter/formatter.go b/logbus/formatter/formatter.go index f8c2ca52ad..41f583a4a1 100644 --- a/logbus/formatter/formatter.go +++ b/logbus/formatter/formatter.go @@ -540,6 +540,10 @@ func (f *Formatter) printBuildFailure() { c.Printf("%s%s\n", msgPrefix, failure.GetErrorMessage()) + if failure.GetHelpMessage() != "" { + c.HelpPrint(failure.GetHelpMessage()) + } + f.lastOutputWasOngoingUpdate = false f.lastOutputWasProgress = false f.lastCommandOutput = nil diff --git a/tests/Earthfile b/tests/Earthfile index 89b8190f89..22debd0db3 100644 --- a/tests/Earthfile +++ b/tests/Earthfile @@ -71,6 +71,7 @@ ga-no-qemu-group4: BUILD +push-arg-test BUILD +ci-arg-test BUILD +gen-dockerfile-test + BUILD +all-privileged-tests BUILD +chown-test BUILD +env-test BUILD +env-home-test @@ -595,6 +596,68 @@ reject-privileged-import-test: DO +RUN_EARTHLY --earthfile=reject-privileged-import.earth --should_fail=true --extra_args="--allow-privileged" --target=+test-reject-copy DO +RUN_EARTHLY --earthfile=reject-privileged-import.earth --should_fail=true --extra_args="--allow-privileged" --target=+test-reject-cmd +all-privileged-tests: + BUILD +privileged-run-test + BUILD +privileged-with-docker-test + BUILD +privileged-if-test + BUILD +privileged-for-test + +privileged-run-test: + # Test that RUN --privileged without -P flag shows improved error message + RUN echo "VERSION 0.8 +test: + FROM alpine:latest + RUN --privileged echo 'this requires privileged mode' +" > Earthfile + DO +RUN_EARTHLY \ + --should_fail=true \ + --target=+test \ + --output_contains="To fix this, use one of the following:" + +privileged-with-docker-test: + # Test that WITH DOCKER without -P flag shows improved error message + RUN echo "VERSION 0.8 +test: + FROM earthbuild/dind:alpine-3.22-docker-28.3.3-r4 + WITH DOCKER + RUN docker ps + END +" > Earthfile + DO +RUN_EARTHLY \ + --should_fail=true \ + --target=+test \ + --output_contains="To fix this, use one of the following:" + +privileged-if-test: + # Test that IF --privileged without -P flag shows improved error message + RUN echo "VERSION 0.8 +test: + FROM alpine:latest + IF --privileged [ -f /proc/self/status ] + RUN echo 'found status' + END +" > Earthfile + DO +RUN_EARTHLY \ + --should_fail=true \ + --target=+test \ + --output_contains="To fix this, use one of the following:" + +privileged-for-test: + # Test that FOR --privileged without -P flag shows improved error message + RUN echo "VERSION 0.8 +test: + FROM alpine:latest + FOR --privileged file IN \$(ls) + RUN echo \$file + END +" > Earthfile + DO +RUN_EARTHLY \ + --should_fail=true \ + --target=+test \ + --output_contains="To fix this, use one of the following:" + + + pass-args-test: COPY pass-args-sub-dir.earth subdir/Earthfile COPY pass-args-root.earth Earthfile From e8f509b9df09ffdd941387ddda85a72e81b3819b Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 15 Apr 2026 12:10:35 +0200 Subject: [PATCH 2/7] Add comment to force CI rebuild --- tests/with-docker/Earthfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/with-docker/Earthfile b/tests/with-docker/Earthfile index cc82451dea..bb57ba8fa9 100644 --- a/tests/with-docker/Earthfile +++ b/tests/with-docker/Earthfile @@ -179,3 +179,5 @@ pre-script-test: WITH DOCKER RUN test -f /the-prescript-was-run END + +# Cache bust for CI From 25a15c4c8094d458abbe74d813939cb7736aa594 Mon Sep 17 00:00:00 2001 From: Daniel Schlegel Date: Wed, 22 Apr 2026 16:03:58 +0200 Subject: [PATCH 3/7] Improve privileged mode error message to show actual target instead of placeholder - Extract target from command line arguments when not available from interpreter error - Show specific command like 'earth -P +foobar' instead of generic 'earth -P +your-target' - Add comprehensive test suite for target extraction logic - Addresses feedback from @janishorsts to provide more meaningful error messages Fixes the issue where users had to mentally substitute the target placeholder with their actual target name in privileged mode error suggestions. --- cmd/earthly/app/run.go | 20 ++++++++++- cmd/earthly/app/run_test.go | 66 +++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/cmd/earthly/app/run.go b/cmd/earthly/app/run.go index 27e9823ad7..5f0ce3f94d 100644 --- a/cmd/earthly/app/run.go +++ b/cmd/earthly/app/run.go @@ -252,13 +252,31 @@ func (app *EarthlyApp) handleError(ctx context.Context, err error, args []string targetInfo = ie.TargetID } + // If no target info from interpreter error, try to extract from args + if targetInfo == "" && len(args) > 1 { + for _, arg := range args[1:] { + if strings.HasPrefix(arg, "+") { + targetInfo = arg + break + } + } + } + userMsg := "This build requires privileged mode." if targetInfo != "" { userMsg = fmt.Sprintf("Target %s requires privileged mode.", targetInfo) } + // Create help message with actual target if available + var flagExample string + if targetInfo != "" { + flagExample = fmt.Sprintf("earth -P %s", targetInfo) + } else { + flagExample = "earth -P +your-target" + } + helpMsg := "To fix this, use one of the following:\n" + - " • Run with the -P flag: earth -P +your-target\n" + + fmt.Sprintf(" • Run with the -P flag: %s\n", flagExample) + " • Set environment variable: export EARTHLY_ALLOW_PRIVILEGED=true\n" + " • Add to config: earth config global.allow_privileged true" diff --git a/cmd/earthly/app/run_test.go b/cmd/earthly/app/run_test.go index 450e80e71d..11072d3487 100644 --- a/cmd/earthly/app/run_test.go +++ b/cmd/earthly/app/run_test.go @@ -1,6 +1,7 @@ package app import ( + "strings" "testing" "github.com/stretchr/testify/require" @@ -42,3 +43,68 @@ func TestRedactSecretsFromArgs(t *testing.T) { require.ElementsMatch(t, testCase.expected, actual) } } + +func TestExtractTargetFromArgs(t *testing.T) { + t.Parallel() + + for _, testCase := range []struct { + name string + args []string + expected string + }{ + { + name: "single target", + args: []string{"earthly", "+target"}, + expected: "+target", + }, + { + name: "target with flags before", + args: []string{"earthly", "--verbose", "+build"}, + expected: "+build", + }, + { + name: "target with flags after", + args: []string{"earthly", "+test", "--ci"}, + expected: "+test", + }, + { + name: "multiple targets - returns first", + args: []string{"earthly", "+first", "+second"}, + expected: "+first", + }, + { + name: "no target", + args: []string{"earthly", "--version"}, + expected: "", + }, + { + name: "empty args", + args: []string{}, + expected: "", + }, + { + name: "only command", + args: []string{"earthly"}, + expected: "", + }, + { + name: "target-like string but not target", + args: []string{"earthly", "not-a-target", "+actual-target"}, + expected: "+actual-target", + }, + } { + t.Run(testCase.name, func(t *testing.T) { + // Simulate the target extraction logic from handleError + var targetInfo string + if len(testCase.args) > 1 { + for _, arg := range testCase.args[1:] { + if strings.HasPrefix(arg, "+") { + targetInfo = arg + break + } + } + } + require.Equal(t, testCase.expected, targetInfo) + }) + } +} From be21121709e9c333226c82ea8c552ed1955eb16e Mon Sep 17 00:00:00 2001 From: danielschlegel Date: Wed, 27 May 2026 14:24:03 +0200 Subject: [PATCH 4/7] Update cmd/earthly/app/run.go Co-authored-by: Janis Horsts --- cmd/earthly/app/run.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cmd/earthly/app/run.go b/cmd/earthly/app/run.go index 9b8fda8872..a76adf6882 100644 --- a/cmd/earthly/app/run.go +++ b/cmd/earthly/app/run.go @@ -268,11 +268,9 @@ func (app *EarthlyApp) handleError(ctx context.Context, err error, args []string } // Create help message with actual target if available - var flagExample string + flagExample := "earth -P +your-target" if targetInfo != "" { flagExample = fmt.Sprintf("earth -P %s", targetInfo) - } else { - flagExample = "earth -P +your-target" } helpMsg := "To fix this, use one of the following:\n" + From fdfb4a7528b96f9d84d1531f9331bcb10e695110 Mon Sep 17 00:00:00 2001 From: danielschlegel Date: Wed, 27 May 2026 14:24:33 +0200 Subject: [PATCH 5/7] Update cmd/earthly/app/run.go Co-authored-by: Janis Horsts --- cmd/earthly/app/run.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/earthly/app/run.go b/cmd/earthly/app/run.go index a76adf6882..c478fa11bd 100644 --- a/cmd/earthly/app/run.go +++ b/cmd/earthly/app/run.go @@ -285,7 +285,7 @@ func (app *EarthlyApp) handleError(ctx context.Context, err error, args []string userMsg, ) - app.BaseCLI.Console().VerboseWarnf("Technical details: %v\n", err.Error()) + app.BaseCLI.Console().VerboseWarnf("Error: %s\n", err.Error()) return 9 case strings.Contains(err.Error(), errutil.EarthlyGitStdErrMagicString): From 32cb5df1566b8da87e9bae909ed5a17cafa01741 Mon Sep 17 00:00:00 2001 From: Daniel Schlegel Date: Wed, 27 May 2026 14:25:59 +0200 Subject: [PATCH 6/7] fix: remove unnecessary line. --- tests/with-docker/Earthfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/with-docker/Earthfile b/tests/with-docker/Earthfile index bb57ba8fa9..01866a692f 100644 --- a/tests/with-docker/Earthfile +++ b/tests/with-docker/Earthfile @@ -178,6 +178,4 @@ pre-script-test: COPY pre-script.sh /usr/share/earthly/dockerd-wrapper-pre-script WITH DOCKER RUN test -f /the-prescript-was-run - END - -# Cache bust for CI + END \ No newline at end of file From 514b999d33eaa8ab01e173ddd203e33038f9dc8e Mon Sep 17 00:00:00 2001 From: Daniel Schlegel Date: Wed, 27 May 2026 20:52:52 +0200 Subject: [PATCH 7/7] fix: resolve golangci-lint failures (wsl_v5, perfsprint, tparallel, govet) --- cmd/earthly/app/run.go | 9 ++++++--- cmd/earthly/app/run_test.go | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/cmd/earthly/app/run.go b/cmd/earthly/app/run.go index c478fa11bd..69b973a1e6 100644 --- a/cmd/earthly/app/run.go +++ b/cmd/earthly/app/run.go @@ -248,6 +248,7 @@ func (app *EarthlyApp) handleError(ctx context.Context, err error, args []string case strings.Contains(err.Error(), "security.insecure is not allowed"): // Extract target info from error if available targetInfo := "" + if ie != nil && isInterpreterError { targetInfo = ie.TargetID } @@ -263,18 +264,20 @@ func (app *EarthlyApp) handleError(ctx context.Context, err error, args []string } userMsg := "This build requires privileged mode." + if targetInfo != "" { - userMsg = fmt.Sprintf("Target %s requires privileged mode.", targetInfo) + userMsg = "Target " + targetInfo + " requires privileged mode." } // Create help message with actual target if available flagExample := "earth -P +your-target" + if targetInfo != "" { - flagExample = fmt.Sprintf("earth -P %s", targetInfo) + flagExample = "earth -P " + targetInfo } helpMsg := "To fix this, use one of the following:\n" + - fmt.Sprintf(" • Run with the -P flag: %s\n", flagExample) + + " • Run with the -P flag: " + flagExample + "\n" + " • Set environment variable: export EARTHLY_ALLOW_PRIVILEGED=true\n" + " • Add to config: earth config global.allow_privileged true" diff --git a/cmd/earthly/app/run_test.go b/cmd/earthly/app/run_test.go index 38f9bc4f5f..b45a92b504 100644 --- a/cmd/earthly/app/run_test.go +++ b/cmd/earthly/app/run_test.go @@ -50,8 +50,8 @@ func TestExtractTargetFromArgs(t *testing.T) { for _, testCase := range []struct { name string - args []string expected string + args []string }{ { name: "single target", @@ -95,6 +95,7 @@ func TestExtractTargetFromArgs(t *testing.T) { }, } { t.Run(testCase.name, func(t *testing.T) { + t.Parallel() // Simulate the target extraction logic from handleError var targetInfo string if len(testCase.args) > 1 { @@ -105,6 +106,7 @@ func TestExtractTargetFromArgs(t *testing.T) { } } } + require.Equal(t, testCase.expected, targetInfo) }) }