home-environment: refresh session variables safely - #9735
Open
khaneliman wants to merge 7 commits into
Open
Conversation
home-manager-ci
Bot
requested review from
SunOfLife1,
midchildan,
rycee and
tadfisher
July 29, 2026 06:35
khaneliman
marked this pull request as draft
July 29, 2026 07:03
khaneliman
force-pushed
the
session-vars-idempotent
branch
4 times, most recently
from
July 29, 2026 07:56
41d81a9 to
9d1699a
Compare
Guarding the whole file behind __HM_SESS_VARS_SOURCED meant a shell that inherited the guard never saw values changed by a later `home-manager switch`. Only the extra section at the end is non-idempotent, so restrict the guard to it and make everything above safe to run again. Plain session variables become unconditional assignments. Search variables get a generated block per value that adds the value only when it is not already present, so re-sourcing introduces no duplicates and entries other tools put in front keep their position. Each value is emitted as its own statement rather than joined and split at runtime, which would corrupt a value containing the separator, and it is only read back through quoted expansions so glob metacharacters stay literal. `programs.zsh.sessionVariables` are re-applied per shell as well: their guard was exported, so a nested shell skipped them while still re-sourcing this file, leaving Home Manager values on top.
`home.sessionSearchVariables` can only prepend, so a variable needing a trailing fallback behind whatever the environment already contains had no idempotent option. Add the counterpart, emitted after the prepend section so a variable receiving both ends up with the prepended values in front and the appended ones last.
khaneliman
force-pushed
the
session-vars-idempotent
branch
from
July 29, 2026 08:01
9d1699a to
e5dc5b4
Compare
The default was a self-referential plain session variable, which is no longer
safe now that plain variables are re-exported on every source. Expressing it as
a constant instead would drop a TERMINFO_DIRS set outside Home Manager, and
deciding whether to emit it from the presence of an attribute in
`home.sessionVariables` is not equivalent to `mkDefault`: a leaf `mkIf false`
leaves the key present while a parent `mkForce {}` removes it, so such a check
inverts against the module system in both directions.
Use the idempotent search variables instead. Home Manager prepends its own
terminfo directory and appends `/usr/share/terminfo`, leaving anything
inherited in between, which reproduces the previous runtime value. It also
fixes two edge cases: the old value tripped `set -u` when TERMINFO_DIRS was
unset, and duplicated `/usr/share/terminfo` when it was already present.
Opting out moves to `targets.darwin.terminfo.enable`, since a plain session
variable now sets the base value rather than replacing the whole mechanism.
Generated search-variable blocks reused __hm_* scratch names in the sourcing shell. Babelfish also translated runtime membership checks into glob-active Fish patterns, so configured *, ? and [ characters could falsely match an inherited entry. Evaluate each merge in command substitution with a sentinel that preserves trailing newlines. Rewrite exact Babelfish fragments into local scratch variables and literal escaped membership checks. Assert every expected rewrite count so a translation change fails the build instead of silently weakening matching. Extend Bourne and Fish runtime coverage for scratch preservation, glob metacharacters, empty values, command substitution, and repeated sourcing.
Make hm-session-vars.sh's guarded extra section the sole owner of nix.sh initialization. Bash startup now re-sources only the Home Manager session file, avoiding duplicate Nix profile PATH and XDG_DATA_DIRS entries when login startup reads both .profile and .bashrc. Add login and direct non-login runtime coverage using a deliberately non-idempotent fake nix.sh with an exported source counter.
khaneliman
force-pushed
the
session-vars-idempotent
branch
from
July 29, 2026 08:08
e5dc5b4 to
a13ec52
Compare
khaneliman
marked this pull request as ready for review
July 29, 2026 10:47
Bash only read hm-session-vars.sh from .profile, so non-login shells inherited stale desktop or tmux values after home-manager switch. Source the idempotent file from .bashrc before bashrcExtra so interactive non-login shells refresh while user code keeps the last word. Skip login shells because .profile applies programs.bash.sessionVariables and profileExtra after generic values. Skip non-interactive shells because Bash is built with SSH_SOURCE_BASHRC and sourcing arbitrary once-per-session code could corrupt ssh, scp, or rsync streams. An exported manifest records generic keys owned by non-null Bash session variables and every exported value changed or unset by profileExtra. Tracking the full exported environment preserves profileExtra precedence even when a later generation introduces a same-named generic variable. Child shells snapshot owned values, refresh the remaining generic variables, then restore login ownership. Sessions inherited from a generation without a manifest conservatively keep existing generated values when profileExtra is configured. Bash before 4.2 also keeps inherited login values when ownership may overlap because it lacks the associative arrays, declare -g, and [[ -v ]] needed for a safe merge. Bash-only bookkeeping stays in version-gated case arms so dash and other POSIX readers can continue sourcing the generated .profile. Add nested-shell, future-key, attributed-export, pre-manifest, legacy-version, repeated-source, null-value, unset-value, POSIX-profile, and non-interactive runtime coverage. Update the fabric-ai Bash golden because every enabled Bash configuration now contains the refresh block.
Session variables now behave differently for every user. Document refresh scope, Bash login ownership, the no-reanchor search-path policy, stale removed values, Zsh self-reference migration, and the Darwin TERMINFO_DIRS opt-out. Keep the news entry and option documentation aligned with the release highlight, including the compatibility boundary that command and arithmetic substitutions remain live in double-quoted search entries.
khaneliman
force-pushed
the
session-vars-idempotent
branch
from
July 31, 2026 04:16
7b6196b to
b2ebc8a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
hm-session-vars.shuses a once-per-session guard today. Long-lived desktop and tmux processes carry that guard into new shells, so values changed byhome-manager switchcan remain stale until logout.This change makes generated session variables safe to source again:
nix.shonce, Fish preserves literal path entries, and Darwin retains inheritedTERMINFO_DIRSentries.Issues and prior pull requests
neovim.defaultEditorhas no effect #4559.home.sessionPathis not being applied #3417.Compatibility
Most existing configurations keep their current behavior. Two intentional migrations are documented in the news entry and release notes:
targets.darwin.terminfo.enable.Older sessions without ownership metadata take a conservative path until the next login. Bash versions before 4.2 also preserve inherited values when ownership could overlap.
Validation
nix build .#test-allnix fmt -- --ciChecklist
Change is backwards compatible.
Code formatted with
nix fmtornix-shell -A dev --run treefmt.Code tested through
nix build .#test-allor a targetednix run .#tests -- <pattern>.Test cases updated/added. See example.
Commit messages are formatted like
See CONTRIBUTING for more information and recent commit messages for examples.
If this PR adds a new module
If this PR adds an exciting new feature or contains a breaking change.