From 2e672f6bb02f85b65830f63669b5d774307681f9 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 29 Jul 2026 14:14:13 +0000 Subject: [PATCH] fix(services): re-invoke devbox by executable path, not hardcoded name (#1321) `devbox services start|stop|ls|restart|up` re-invoke devbox inside the computed environment via a generated run script that eval's a command beginning with the literal string "devbox". This required the binary to be named exactly "devbox" and resolvable on PATH; any other name (e.g. a renamed or versioned install) broke services commands with "devbox: command not found". Use os.Executable() to reference the currently running binary (shell- quoted so paths with spaces survive the eval), falling back to "devbox" only if the executable path can't be determined. Fixes #1321. --- internal/devbox/services.go | 20 +++++++++++++++++++- internal/devbox/services_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 internal/devbox/services_test.go diff --git a/internal/devbox/services.go b/internal/devbox/services.go index 3af32a13383..89731cb97af 100644 --- a/internal/devbox/services.go +++ b/internal/devbox/services.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "os" "strconv" "text/tabwriter" @@ -271,7 +272,24 @@ func (d *Devbox) StartProcessManager( // defaults for the `devbox services` scenario. func (d *Devbox) runDevboxServicesScript(ctx context.Context, cmdArgs []string) error { cmdArgs = append([]string{"services"}, cmdArgs...) - return d.RunScript(ctx, devopt.EnvOptions{}, "devbox", cmdArgs) + return d.RunScript(ctx, devopt.EnvOptions{}, devboxBinaryForSelfInvocation(), cmdArgs) +} + +// devboxBinaryForSelfInvocation returns a shell-quoted reference to the +// currently running devbox binary. `devbox services ...` re-invokes devbox +// inside the computed environment, and using the actual executable path (rather +// than a hardcoded "devbox") ensures this works even when the binary has been +// installed or renamed to something other than "devbox". If the executable path +// can't be determined, it falls back to "devbox", relying on a PATH lookup as +// before. See https://github.com/jetify-com/devbox/issues/1321. +func devboxBinaryForSelfInvocation() string { + exe, err := os.Executable() + if err != nil { + return "devbox" + } + // Quote the path so it survives being eval'd by the generated run script + // (e.g. when the path contains spaces). + return strconv.Quote(exe) } func (d *Devbox) ShowProcessComposePort(ctx context.Context, writer io.Writer) error { diff --git a/internal/devbox/services_test.go b/internal/devbox/services_test.go new file mode 100644 index 00000000000..4821c67dc07 --- /dev/null +++ b/internal/devbox/services_test.go @@ -0,0 +1,32 @@ +// Copyright 2024 Jetify Inc. and contributors. All rights reserved. +// Use of this source code is governed by the license in the LICENSE file. + +package devbox + +import ( + "os" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestDevboxBinaryForSelfInvocation verifies that `devbox services ...` +// re-invokes devbox using the path to the currently running binary rather than +// a hardcoded "devbox" that must be resolvable on PATH. This is a regression +// guard for https://github.com/jetify-com/devbox/issues/1321, where services +// commands broke whenever the binary was installed or renamed to something +// other than "devbox". +func TestDevboxBinaryForSelfInvocation(t *testing.T) { + exe, err := os.Executable() + require.NoError(t, err) + + got := devboxBinaryForSelfInvocation() + + // It should reference the actual running binary (shell-quoted), not the + // literal command name "devbox". + assert.Equal(t, strconv.Quote(exe), got) + assert.NotEqual(t, "devbox", got) + assert.NotEqual(t, strconv.Quote("devbox"), got) +}