From 1645e43dabe68cfcec16d2bfc0af3abf9073dc06 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 10 Jun 2026 14:13:25 +0000 Subject: [PATCH] fix(direnv): set DEVBOX_SHELL_ENABLED when entering env via shellenv --init-hook The direnv integration loads the Devbox environment by evaluating `devbox shellenv --init-hook`, but that path never set DEVBOX_SHELL_ENABLED. As a result `$DEVBOX_SHELL_ENABLED` was empty in direnv-activated shells, contradicting the documented behavior that it is set to 1 whenever the environment is loaded. Set DEVBOX_SHELL_ENABLED=1 in EnvExports when init hooks are run, mirroring what Shell() and RunScript() already do. The flag is gated on RunHooks so that the global integration (`devbox global shellenv`, which does not run init hooks) does not set it in every shell and break shell-inception detection. Adds a regression testscript asserting that `devbox shellenv --init-hook` exports the variable while plain `devbox shellenv` does not. Fixes #2746 --- internal/devbox/devbox.go | 12 ++++++++++++ testscripts/shell/shellenv_init_hook.test.txt | 13 +++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 testscripts/shell/shellenv_init_hook.test.txt diff --git a/internal/devbox/devbox.go b/internal/devbox/devbox.go index 8ac001233d3..7c5af9161cf 100644 --- a/internal/devbox/devbox.go +++ b/internal/devbox/devbox.go @@ -379,6 +379,18 @@ func (d *Devbox) EnvExports(ctx context.Context, opts devopt.EnvExportsOpts) (st return "", err } + // When we also run the init hooks we are fully entering the Devbox + // environment (this is what the direnv integration does via + // `devbox shellenv --init-hook`), so mark the shell as Devbox-enabled. + // This mirrors Shell() and RunScript(), and matches the documented + // behavior that DEVBOX_SHELL_ENABLED is set whenever the environment is + // loaded. We intentionally gate this on RunHooks so that the global + // integration (`devbox global shellenv`, which does not run init hooks) + // does not set it in every shell and break shell-inception detection. + if opts.RunHooks { + envs[envir.DevboxShellEnabled] = "1" + } + // Use the appropriate export format based on shell type var envStr string if opts.ShellFormat == devopt.ShellFormatNushell { diff --git a/testscripts/shell/shellenv_init_hook.test.txt b/testscripts/shell/shellenv_init_hook.test.txt new file mode 100644 index 00000000000..72b92e5acd4 --- /dev/null +++ b/testscripts/shell/shellenv_init_hook.test.txt @@ -0,0 +1,13 @@ +exec devbox init + +# When run with --init-hook (as the direnv integration does via +# `devbox shellenv --init-hook`), we are fully entering the Devbox +# environment, so DEVBOX_SHELL_ENABLED must be exported. See issue #2746. +exec devbox shellenv --init-hook +stdout 'export DEVBOX_SHELL_ENABLED="1";' + +# Plain shellenv (used by the global integration `devbox global shellenv`, +# which runs in every shell) must NOT set DEVBOX_SHELL_ENABLED, otherwise +# shell-inception detection would break in every shell. +exec devbox shellenv +! stdout 'DEVBOX_SHELL_ENABLED'