Skip to content

home-environment: refresh session variables safely - #9735

Open
khaneliman wants to merge 7 commits into
nix-community:masterfrom
khaneliman:session-vars-idempotent
Open

home-environment: refresh session variables safely#9735
khaneliman wants to merge 7 commits into
nix-community:masterfrom
khaneliman:session-vars-idempotent

Conversation

@khaneliman

@khaneliman khaneliman commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Description

hm-session-vars.sh uses a once-per-session guard today. Long-lived desktop and tmux processes carry that guard into new shells, so values changed by home-manager switch can remain stale until logout.

This change makes generated session variables safe to source again:

  • Plain variables refresh to their current Home Manager values.
  • PATH-like variables add missing entries without duplicating or reordering entries already present.
  • A new append option supports search-path fallbacks that belong at the end.
  • Bash refreshes interactive non-login shells while preserving values owned by login startup.
  • Generic Linux sources nix.sh once, Fish preserves literal path entries, and Darwin retains inherited TERMINFO_DIRS entries.

Issues and prior pull requests

Compatibility

Most existing configurations keep their current behavior. Two intentional migrations are documented in the news entry and release notes:

  • Self-referential plain session variables should use the search-variable options instead.
  • Darwin users who opt out should use 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-all
  • Focused session-variable and shell tests: 14/14
  • nix fmt -- --ci
  • HTML and manpage documentation builds
  • Darwin module cross-evaluation

Checklist

  • Change is backwards compatible.

  • Code formatted with nix fmt or nix-shell -A dev --run treefmt.

  • Code tested through nix build .#test-all or a targeted nix run .#tests -- <pattern>.

  • Test cases updated/added. See example.

  • Commit messages are formatted like

    {component}: {description}
    
    {long description}
    

    See CONTRIBUTING for more information and recent commit messages for examples.

  • If this PR adds a new module

    • Added myself as module maintainer. See example.
    • Generate a news entry. See News
    • Basic tests added. See Tests
  • If this PR adds an exciting new feature or contains a breaking change.

    • Generate a news entry. See News

@github-actions github-actions Bot added the shell label Jul 29, 2026
@khaneliman
khaneliman marked this pull request as draft July 29, 2026 07:03
@khaneliman
khaneliman force-pushed the session-vars-idempotent branch 4 times, most recently from 41d81a9 to 9d1699a Compare July 29, 2026 07:56
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
khaneliman force-pushed the session-vars-idempotent branch from 9d1699a to e5dc5b4 Compare July 29, 2026 08:01
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
khaneliman force-pushed the session-vars-idempotent branch from e5dc5b4 to a13ec52 Compare July 29, 2026 08:08
@khaneliman
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
khaneliman force-pushed the session-vars-idempotent branch from 7b6196b to b2ebc8a Compare July 31, 2026 04:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: neovim.defaultEditor has no effect bug: hm-session-vars forces variables to be only set once, forcing log-out or reboot for Gnome users

1 participant