execenv

package
v0.70.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 4, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package execenv resolves the environment the app runs subprocesses with, and answers what a variable would hold in the user's terminal.

A desktop launch inherits the launcher's environment, not a shell's: macOS starts an .app bundle with PATH=/usr/bin:/bin:/usr/sbin:/sbin. Session hooks and shell actions are the user's own commands, written against the PATH their terminal has, so running them with the inherited one fails on anything a package manager, a version manager or a language toolchain installed (ADR subprocess-environment). The same gap hides hive's own environment overrides from the app, which is what Getenv answers (ADR hive-env-overrides-resolve-through-the-login-shell).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func SearchDirs

func SearchDirs() []string

SearchDirs returns those prefixes plus the per-user ones. It is where a binary lives when the environment does not say: tmuxbin searches the list for one executable (ADR tmux-discovery), and the resolver appends it to PATH.

Types

type Options

type Options struct {
	Logger zerolog.Logger
	// Shell is the login shell to ask. Empty reads $SHELL; still empty skips
	// the probe.
	Shell string
	// Timeout bounds one probe. Zero means defaultTimeout.
	Timeout time.Duration
	// Probe is the seam tests replace. Zero runs the real login shell.
	Probe func(ctx context.Context, shell string) (map[string]string, error)
	// Pinned wins over both this process's environment and the login shell's.
	Pinned map[string]string
}

Options configures a Resolver. The zero value probes $SHELL with the default timeout and logs nothing.

type Resolver

type Resolver struct {
	// contains filtered or unexported fields
}

Resolver answers what PATH a subprocess runs with. It asks the user's login shell once per run and remembers the answer — a shell's PATH does not change under a running app, and re-running a login shell per hook command would charge every command that shell's startup cost.

func NewResolver

func NewResolver(opts Options) *Resolver

func (*Resolver) Environ

func (r *Resolver) Environ(ctx context.Context) []string

Environ returns the environment subprocesses run with: this process's, with PATH replaced by Path and the login shell's remaining variables filling names this process does not define. A launch that names a variable is more specific than a startup file, so the process wins — which keeps a stale rc file from shadowing a HIVE_DESKTOP_* override the app was started with.

The result is shared between callers: append to it, never write into it.

func (*Resolver) Getenv

func (r *Resolver) Getenv(ctx context.Context, name string) string

Getenv answers a variable the way the user's terminal would: this process's value when it has one, otherwise the login shell's. A variable exported from a startup file is invisible to a launched .app, so a config override the CLI reads from the environment is only reachable through the probe (ADR hive-env-overrides-resolve-through-the-login-shell).

func (*Resolver) LookPath

func (r *Resolver) LookPath(ctx context.Context, name string) (string, error)

LookPath resolves a command name against the same PATH its child will run with. It exists because os/exec does not: exec.Command searches the PATH of the process that calls it and ignores Cmd.Env, which is the one this package is replacing. A name that is already a path is returned untouched.

func (*Resolver) Path

func (r *Resolver) Path(ctx context.Context) string

Path returns the PATH value subprocesses run with: what the login shell reports, then what this process inherited, then the package-manager prefixes, first occurrence winning. A probe that fails is not fatal — the inherited PATH still runs everything in /usr/bin, which is what the app had before.

func (*Resolver) ProbeErr

func (r *Resolver) ProbeErr() error

ProbeErr returns why the login shell did not answer, or nil when it answered or was not asked yet. Getenv returns "" for a variable in both cases, so a caller that falls back on "" uses this to tell an unset variable from a shell that could not be read.

func (*Resolver) SetLogger

func (r *Resolver) SetLogger(logger zerolog.Logger)

SetLogger replaces the logger the probe reports to. A program that resolves its startup paths before its final logger exists builds the Resolver early and hands it the final logger here.

func (*Resolver) UserPath added in v0.70.0

func (r *Resolver) UserPath(ctx context.Context) string

UserPath is Path without the package-manager and per-user prefixes the app appends: what the user's own terminal searches. A caller that asks whether the user can run a command by name needs it, because Path always lists ~/.local/bin and would answer yes for a directory the shell never reads.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL