Skip to content

fix(shellenv): only export env vars Devbox actually modifies#2902

Open
mikeland73 wants to merge 1 commit into
mainfrom
claude/focused-goldberg-yqnnas
Open

fix(shellenv): only export env vars Devbox actually modifies#2902
mikeland73 wants to merge 1 commit into
mainfrom
claude/focused-goldberg-yqnnas

Conversation

@mikeland73

Copy link
Copy Markdown
Collaborator

Summary

Fixes #2826.

devbox shellenv and devbox global shellenv emit an export statement for every variable in the computed environment — including inherited variables that Devbox never touched, such as HOSTNAME, LANG, and PROFILEREAD. Re-exporting an unchanged variable is redundant, and it breaks in shells that mark some inherited variables as read-only. On openSUSE, eval "$(devbox global shellenv)" failed on every prompt with:

(eval):55: read-only variable: PROFILEREAD

Root cause

Devbox.computeEnv (internal/devbox/devbox.go) builds the environment by copying the entire parent environment (os.Environ()) and then layering Nix- and Devbox-specific variables on top. EnvExports then runs exportify over that whole map, so unchanged variables like PROFILEREAD are re-emitted as export PROFILEREAD="true";. When that output is eval'd in a shell that has marked PROFILEREAD read-only, the assignment errors out.

Fix

Filter the computed environment down to just the variables whose value differs from the current environment (or that are new) before formatting the export statements:

  • New helper onlyModifiedEnvs(envs, baseEnv) in internal/devbox/envvars.go returns only entries that are absent from, or changed relative to, baseEnv.
  • EnvExports now calls onlyModifiedEnvs(envs, envir.PairsToMap(os.Environ())) before choosing the bash/nushell export format.

The output of EnvExports is meant to be eval'd in the caller's shell (devbox shellenv, and devbox shell --print-env used by direnv), so dropping variables that already hold the same value is a no-op in the normal case and avoids the read-only error. Variables Devbox actually changes — PATH, XDG_DATA_DIRS, DEVBOX_*, Nix and plugin/config variables, the shellenv hash — all differ from the parent value and are still exported.

The interactive devbox shell path (which sources a generated shellrc in a child shell via shell.go) is intentionally unchanged; only EnvExports is affected.

How was it tested?

  • Added internal/devbox/envvars_test.go::TestOnlyModifiedEnvs, covering unchanged variables being dropped, changed/new/emptied variables being kept, and empty-base-env behavior.
  • go test ./internal/devbox/ -run TestOnlyModifiedEnvs — passes.
  • go build ./internal/devbox/, go vet ./internal/devbox/, and gofmt — all clean.
  • Verified the existing testscripts/shell/shellenv.test.txt flow is unaffected: it sources the export PATH= line from devbox shellenv, and PATH is always modified by Devbox, so it is still emitted.

cc @gasuketsu (issue reporter) — thanks for the clear write-up and pinpointing the read-only variable failure.

Community Contribution License

All community contributions in this pull request are licensed to the project
maintainers under the terms of the
Apache 2 License.

By creating this pull request, I represent that I have the right to license the
contributions to the project maintainers under the Apache 2 License as stated in
the
Community Contribution License.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WiTxPixVaWVbpBsZf1DLeG


Generated by Claude Code

`devbox shellenv` and `devbox global shellenv` emitted an `export` statement
for every variable in the computed environment, including inherited variables
that Devbox never changed (e.g. HOSTNAME, LANG, PROFILEREAD). Re-exporting an
unchanged variable is redundant, and it breaks in shells that mark some
inherited variables as read-only: on openSUSE, `eval "$(devbox global
shellenv)"` failed on every prompt with `read-only variable: PROFILEREAD`.

Filter the computed environment down to the variables whose value differs from
the current environment (or that are new) before formatting the export
statements. The output is meant to be eval'd in the caller's shell, so
skipping variables that already have the same value is a no-op in the normal
case and avoids the read-only error.

The interactive `devbox shell` code path (which sources a generated shellrc in
a child shell) is unchanged; only `EnvExports`, used by `devbox shellenv` and
`devbox shell --print-env`, is affected.

Fixes #2826

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WiTxPixVaWVbpBsZf1DLeG
Copilot AI review requested due to automatic review settings July 4, 2026 14:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates devbox shellenv/devbox global shellenv to emit export statements only for environment variables that Devbox actually changes relative to the current environment, avoiding redundant re-exports that can fail on shells with read-only inherited variables (e.g. PROFILEREAD on openSUSE).

Changes:

  • Added onlyModifiedEnvs(envs, baseEnv) helper to filter env maps down to new/changed variables.
  • Updated Devbox.EnvExports to filter computed envs against os.Environ() before formatting bash/nushell output.
  • Added unit tests for onlyModifiedEnvs covering unchanged, changed/new, empty-value, and empty-base behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
internal/devbox/envvars.go Adds onlyModifiedEnvs helper to filter environment variables by diff vs. a base env map.
internal/devbox/envvars_test.go Introduces focused unit tests validating the filtering behavior.
internal/devbox/devbox.go Applies the filtering in EnvExports so shellenv output avoids re-exporting unchanged inherited vars.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Why devbox shellenv and devbox global shellenv export all existing envvars ?

3 participants