Skip to content

Commit 9d11ab8

Browse files
fix: propagate context cancellation during license pre-flight
The offline-degradation branch in validateLicense treated any non-LicenseError as an unreachable server. A cancelled caller context (Ctrl+C) also surfaces there, so it was swallowed — printing a misleading "Could not reach the license server" warning and letting the start continue. Propagate ctx.Err() before degrading; the client's own request timeout is distinct from ctx and still falls through to the offline fallback. Generated with [Linear](https://linear.app/localstack/issue/DEVX-703/support-enterprise-environments-that-cannot-pull-from-docker-hub#agent-session-77e3f9fc) Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
1 parent d15dacb commit 9d11ab8

2 files changed

Lines changed: 34 additions & 0 deletions

File tree

internal/container/start.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,12 @@ func validateLicense(ctx context.Context, sink output.Sink, opts StartOptions, c
605605
licenseResp, err := opts.PlatformClient.GetLicense(ctx, licenseReq)
606606
if err != nil {
607607
sink.Emit(output.SpinnerStop())
608+
// A cancelled caller context (e.g. Ctrl+C) is not an offline condition —
609+
// propagate it instead of degrading. The client's own request timeout is
610+
// distinct from ctx and still falls through to the offline fallback below.
611+
if ctx.Err() != nil {
612+
return ctx.Err()
613+
}
608614
var licErr *api.LicenseError
609615
if !errors.As(err, &licErr) {
610616
// The license server responded with no definitive verdict — the request

internal/container/start_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,34 @@ func TestValidateLicense_FailsOnServerRejection(t *testing.T) {
492492
assert.Contains(t, err.Error(), "license validation failed")
493493
}
494494

495+
func TestValidateLicense_PropagatesContextCancellation(t *testing.T) {
496+
// A cancelled caller context (Ctrl+C) must surface as cancellation, not be
497+
// mistaken for an offline license server.
498+
opts := StartOptions{
499+
PlatformClient: api.NewPlatformClient("http://127.0.0.1:1", log.Nop()),
500+
Logger: log.Nop(),
501+
Telemetry: telemetry.New("", true),
502+
}
503+
c := runtime.ContainerConfig{
504+
EmulatorType: config.EmulatorAWS,
505+
ProductName: "localstack-pro",
506+
Tag: "2026.4",
507+
Image: "localstack/localstack-pro:2026.4",
508+
}
509+
510+
ctx, cancel := context.WithCancel(context.Background())
511+
cancel()
512+
513+
var out bytes.Buffer
514+
sink := output.NewPlainSink(&out)
515+
516+
err := validateLicense(ctx, sink, opts, c, "tok", filepath.Join(t.TempDir(), "license.json"))
517+
518+
require.ErrorIs(t, err, context.Canceled)
519+
assert.NotContains(t, out.String(), "Could not reach the license server",
520+
"a cancellation must not be reported as an unreachable license server")
521+
}
522+
495523
func TestStartContainers_SnowflakeLicenseError(t *testing.T) {
496524
ctrl := gomock.NewController(t)
497525
mockRT := runtime.NewMockRuntime(ctrl)

0 commit comments

Comments
 (0)