Skip to main content

Understanding Environment Inheritance in tmux

A new tmux session is not necessarily a clean environment.

That statement sounds strange at first. If I open a new terminal, remove a variable, and create a new tmux session, I might reasonably expect the new session to inherit the environment of the shell from which I ran tmux new-session.

That is not always what happens.

tmux is built around a long-running server process. The shell that runs a tmux command is a client of that server. If the server already exists, creating a new session does not create a new tmux server, and it does not simply copy the complete environment of the current client shell.

To understand what a new tmux process inherits, we need to distinguish several environments that are easy to conflate.

The inheritance model​

The basic inheritance path is:

shell that initially starts the tmux server
β”‚
β–Ό
tmux global environment
β”‚
β”‚ merged with
β–Ό
tmux session environment
β”‚
β–Ό
newly created window or pane
β”‚
β–Ό
shell startup files and commands
β”‚
β–Ό
environment of the running shell process

When the tmux server first starts, it copies the environment of the client process that started it into tmux's global environment.

Each tmux session can also have its own environment entries. When tmux creates a new process in a window or pane, it merges the global environment with the session environment. If the same variable exists in both places, the session value takes precedence.

After the process starts, it has its own process environment. Changing tmux's stored environment later does not retroactively modify shells or programs that are already running.

That gives us at least four distinct perspectives:

  1. The environment of the shell outside tmux.
  2. The global environment stored by the tmux server.
  3. The environment associated with a particular tmux session.
  4. The environment of a shell already running inside a pane.

Those environments can contain different values for the same variable.

A simple example​

Start with no tmux server running:

tmux kill-server 2>/dev/null

Now set a variable and start tmux:

export APP_ENV=staging
tmux new-session -s first

Inside the tmux session:

printf '%s\n' "$APP_ENV"

The result should be:

staging

That part is unsurprising. The tmux server started from a shell containing APP_ENV=staging, so the value entered tmux's global environment and was passed to the new shell.

Detach from the session:

Ctrl-b d

Now, in the outside shell, remove the variable:

unset APP_ENV

Confirm that the outside shell no longer has it:

printenv APP_ENV

No output should be produced.

Create another tmux session:

tmux new-session -s second

Inside the new session, inspect the variable:

printf '%s\n' "$APP_ENV"

You may still see:

staging

The second session was new, but the tmux server was not. The server was still holding the global environment it captured earlier.

The relevant boundary was the lifetime of the server, not merely the lifetime of the session.

Inspecting the current shell environment​

Inside or outside tmux, ordinary shell commands inspect the environment of the current process:

env

or:

printenv

To inspect one variable:

printenv APP_ENV

Shell expansion can also display a value:

printf '%s\n' "$APP_ENV"

There is a small but useful distinction here.

printenv APP_ENV indicates whether APP_ENV exists in the process environment. By contrast:

printf '%s\n' "$APP_ENV"

prints an empty line both when the variable is unset and when it is set to an empty string.

To distinguish those cases in Bash:

if [[ -v APP_ENV ]]; then
printf 'APP_ENV is set to: %q\n' "$APP_ENV"
else
printf 'APP_ENV is unset\n'
fi

These commands describe the environment of the shell in which they run. They do not necessarily describe what tmux currently has stored.

A shell may have changed a variable after it started:

export APP_ENV=development

That updates the shell and the commands it subsequently launches. It does not automatically update tmux's global or session environment.

Inspecting tmux's global environment​

To see the global environment stored by the tmux server:

tmux show-environment -g

The shorter command alias also works:

tmux showenv -g

To inspect one variable:

tmux show-environment -g APP_ENV

For example:

APP_ENV=staging

This command can be run from inside or outside tmux. In both cases, it communicates with the tmux server and asks the server to display its stored global value.

That makes it possible to compare the current shell with the server:

printf 'current shell: '
printenv APP_ENV || printf '<unset>\n'

printf 'tmux global: '
tmux show-environment -g APP_ENV 2>/dev/null || printf '<unset>\n'

The result might be:

current shell: development
tmux global: APP_ENV=staging

There is no contradiction. The two commands are inspecting different environments.

Inspecting a session environment​

To inspect the environment associated with a particular session:

tmux show-environment -t development

To inspect one variable:

tmux show-environment -t development APP_ENV

The target does not have to be the session to which the current client is attached. You can inspect another session by name without attaching to it:

tmux show-environment -t staging APP_ENV
tmux show-environment -t production APP_ENV

To find the name of the current session from inside tmux:

tmux display-message -p '#S'

That allows a small diagnostic:

session_name=$(tmux display-message -p '#S')

printf 'shell process:\n'
printenv APP_ENV || printf '<unset>\n'

printf '\ntmux global environment:\n'
tmux show-environment -g APP_ENV 2>/dev/null || printf '<unset>\n'

printf '\ntmux session environment:\n'
tmux show-environment -t "$session_name" APP_ENV 2>/dev/null \
|| printf '<no session-specific entry>\n'

The session environment should not be thought of as a second, unrelated complete process environment.

tmux maintains a global environment and a session environment. When it creates a process, it combines them. A session entry overrides a global entry with the same name.

Conceptually:

global:
APP_ENV=staging
LOG_LEVEL=info

session:
APP_ENV=development

merged environment for a new process:
APP_ENV=development
LOG_LEVEL=info

The session-specific APP_ENV wins. LOG_LEVEL still comes from the global environment.

The environment of an existing pane​

Once tmux has launched a shell in a pane, that shell is an ordinary running process with its own environment.

Suppose a pane starts with:

printenv APP_ENV

and prints:

staging

From another shell, change tmux's global value:

tmux set-environment -g APP_ENV production

Confirm that tmux now holds the new value:

tmux show-environment -g APP_ENV

The output is:

APP_ENV=production

Return to the shell that was already running and inspect its value again:

printenv APP_ENV

It will still report:

staging

The existing process was not modified.

Now create a new window in that session:

tmux new-window

In the new shell:

printenv APP_ENV

The new process may report:

production

tmux provides an initial environment when it starts a process. It does not continuously synchronize the environments of processes that are already running.

New sessions, windows, and panes​

The distinction between tmux objects matters:

server
└── session
β”œβ”€β”€ window
β”‚ β”œβ”€β”€ pane
β”‚ └── pane
└── window
└── pane

The server owns the global environment.

A session can carry session-specific environment state.

Windows and panes contain processes. When tmux starts one of those processes, it constructs an initial environment from the global and session environments.

Therefore:

  • Starting a new session does not necessarily start a new server.
  • Starting a new window creates a new process environment.
  • Splitting a pane creates another new process environment.
  • Existing pane processes keep the environments they already have.

A command issued by a shell inside a pane is a separate matter. A child process launched by that shell normally inherits the shell's current environment, not a freshly reconstructed environment directly from tmux.

For example:

export APP_ENV=local
./run-server

./run-server inherits APP_ENV=local from the shell.

But creating a new tmux pane:

tmux split-window

causes tmux to launch the new pane's process using tmux's stored environment model. It does not simply clone every environment change made interactively in the current pane.

Changing the global environment​

Set a global variable:

tmux set-environment -g APP_ENV staging

Inspect it:

tmux show-environment -g APP_ENV

Change it:

tmux set-environment -g APP_ENV production

Remove the global entry:

tmux set-environment -gu APP_ENV

The long form is:

tmux set-environment -g -u APP_ENV

The -g flag selects the global environment. The -u flag unsets the entry.

These changes affect the environment tmux constructs for future processes. They do not rewrite the environments of already-running processes.

Changing a session environment​

Set a value for a particular session:

tmux set-environment -t development APP_ENV development

Inspect it:

tmux show-environment -t development APP_ENV

Remove the session entry:

tmux set-environment -u -t development APP_ENV

Without -g, set-environment operates on a session environment.

A session-specific value can override a global value:

tmux set-environment -g APP_ENV staging
tmux set-environment -t development APP_ENV development

Now the stored values are:

tmux show-environment -g APP_ENV
tmux show-environment -t development APP_ENV

Conceptually:

global APP_ENV:      staging
development session: development

A new process in the development session receives the session-specific value.

A new process in another session, without its own APP_ENV entry, receives the global value.

Unset variables and removal markers​

tmux can represent more than a simple NAME=value entry.

Its environment output may include a line such as:

-APP_ENV

That does not mean the variable has a value beginning with a hyphen.

The leading - indicates that the variable is marked for removal from the environment of a newly created process.

This distinction matters when a variable exists globally but a session needs to ensure that it is absent.

For recent versions of tmux, set-environment provides both -u and -r:

tmux set-environment -u APP_ENV

unsets an entry from the selected tmux environment, while:

tmux set-environment -r APP_ENV

marks the variable to be removed before tmux starts a new process.

To mark a global variable for removal:

tmux set-environment -gr APP_ENV

To mark it for removal in one session:

tmux set-environment -r -t development APP_ENV

Inspecting the environment may then show:

-APP_ENV

Because tmux command flags can differ across older versions, check the installed manual before depending on -r:

man tmux

Then search for:

set-environment

Shell-formatted output​

show-environment also supports shell-formatted output:

tmux show-environment -s

For one variable:

tmux show-environment -s APP_ENV

Instead of producing:

APP_ENV=staging

it produces output suitable for a Bourne-style shell, such as an export or unset command.

That makes this possible:

eval "$(tmux show-environment -s APP_ENV)"

This explicitly imports the value currently stored by tmux into the current shell.

That is fundamentally different from tmux modifying an existing shell automatically. The shell is reading tmux's output and executing the corresponding shell command itself.

Because eval executes generated shell text, it should be used deliberately. Inspect the output first when experimenting:

tmux show-environment -s APP_ENV

What update-environment does​

tmux has a session option named update-environment.

Inspect it with:

tmux show-options -g update-environment

Depending on the version and configuration, the result may contain variables such as:

DISPLAY
SSH_AUTH_SOCK
SSH_ASKPASS
WINDOWID
XAUTHORITY

The exact list is configurable.

The important point is that update-environment is a list of selected variable names. It is not an instruction to copy the entire environment of every client into tmux.

When a new session is created or an existing session is attached, tmux can update those selected variables in the session environment from the relevant client environment.

That behavior is useful for variables whose values commonly change between terminal connections, particularly display or authentication-agent variables.

It does not mean that an arbitrary application variable will be synchronized:

export APP_ENV=production
tmux attach-session -t development

Unless APP_ENV is included in update-environment, attaching from that shell does not imply that tmux will update its stored APP_ENV.

To add a variable to the option:

tmux set-option -ga update-environment ' APP_ENV'

The leading space separates the new item from the existing list.

Inspect the result:

tmux show-options -g update-environment

This should be done carefully. Automatically updating project-specific variables during attachment can make a session's behavior depend on which client attached most recently.

The client environment​

The shell that invokes a tmux command is the environment of a tmux client process.

You can inspect that shell directly before invoking tmux:

env | sort

or capture it:

env | sort > /tmp/outside-tmux.env

Inside a pane:

env | sort > /tmp/inside-tmux.env

Compare them:

diff -u /tmp/outside-tmux.env /tmp/inside-tmux.env

Some differences are expected. tmux sets or changes variables used to identify and configure the terminal environment, including variables such as TMUX, TMUX_PANE, and TERM.

The comparison is still useful for finding application-specific variables that entered the pane unexpectedly.

For a narrower comparison:

comm -3 \
<(env | sort) \
<(tmux show-environment -g | sort)

This compares the current shell environment with tmux's global environment.

The output should be interpreted carefully because:

  • tmux's output uses its own representation for removed variables;
  • the current shell may have added or changed variables after startup;
  • shell startup files may alter variables after tmux launches the shell;
  • tmux may set terminal-specific variables itself.

Identifying the server​

The command:

tmux list-sessions

shows sessions on the default tmux server.

If it succeeds, a server already exists. Running:

tmux new-session -s another

normally creates another session on that existing server.

If no server exists, tmux reports an error similar to:

no server running

You can use a quiet check:

if tmux list-sessions >/dev/null 2>&1; then
printf 'a tmux server is running\n'
else
printf 'no tmux server is running\n'
fi

The server's persistence explains why environment state can survive after every client has detached.

A detached session is still owned by the running server. Even if no terminal is currently displaying it, the server and its stored state remain alive.

Named servers and sockets​

tmux can run multiple independent servers.

The -L option selects a socket name:

tmux -L development new-session -s main

Commands using the default server do not address that server:

tmux list-sessions

To communicate with the named server, use the same socket name:

tmux -L development list-sessions

Its global environment is separate:

tmux -L development show-environment -g

Another server can have another environment:

tmux -L production new-session -s main
tmux -L production show-environment -g

This gives us another boundary:

default tmux server
global environment A

development tmux server
global environment B

production tmux server
global environment C

A named server is useful when a group of sessions should not share the environment or lifecycle of the default server.

Starting a server from a controlled environment​

Removing variables from the shell before running tmux new-session is sufficient only when that command will start a new server.

For example:

env -u APP_ENV tmux new-session -s clean

If no tmux server exists, the new server starts without APP_ENV.

If a server already exists, however, the client connects to that server. Removing APP_ENV from the client does not automatically remove it from the server's stored global environment.

A named server makes the boundary explicit:

tmux -L isolated kill-server 2>/dev/null

env -u APP_ENV \
tmux -L isolated new-session -s clean

For a more aggressively controlled environment:

tmux -L isolated kill-server 2>/dev/null

env -i \
HOME="$HOME" \
USER="$USER" \
PATH="$PATH" \
TERM="$TERM" \
SHELL="$SHELL" \
tmux -L isolated new-session -s clean

env -i begins with an empty environment. The command then supplies only the variables explicitly listed.

This is useful for experimentation, but it can also remove variables required for locale handling, desktop integration, SSH agents, graphical applications, or other system features.

The point is not that every tmux session should be started with env -i. The point is that a genuinely controlled tmux environment requires control over the creation of the server, not merely the creation of a session on an existing server.

A diagnostic script​

The following script compares one variable from several perspectives:

#!/usr/bin/env bash

set -u

variable=${1:-APP_ENV}

printf 'Variable: %s\n\n' "$variable"

printf 'Current process environment:\n'
if printenv "$variable"; then
:
else
printf '<unset>\n'
fi

printf '\nTmux global environment:\n'
if tmux show-environment -g "$variable" 2>/dev/null; then
:
else
printf '<unset or no tmux server>\n'
fi

if [[ -n ${TMUX:-} ]]; then
session_name=$(tmux display-message -p '#S')

printf '\nCurrent session: %s\n' "$session_name"
if tmux show-environment -t "$session_name" "$variable" 2>/dev/null; then
:
else
printf '<no session-specific entry>\n'
fi
else
printf '\nCurrent session:\n'
printf '<not running inside tmux>\n'
fi

Save it as:

inspect-tmux-environment

Make it executable:

chmod +x inspect-tmux-environment

Then run:

./inspect-tmux-environment APP_ENV

A result might look like:

Variable: APP_ENV

Current process environment:
development

Tmux global environment:
APP_ENV=staging

Current session: application
APP_ENV=production

That result is possible because each line describes a different layer.

The current shell has development.

The tmux server's global environment has staging.

The session has a session-specific override of production.

A new process created by tmux in that session should therefore initially receive production. A command launched directly by the current shell receives development.

A reproducible experiment​

The entire inheritance model can be demonstrated with an isolated tmux server.

First, destroy any previous experimental server:

tmux -L environment-test kill-server 2>/dev/null

Start the server with a global value:

APP_ENV=global \
tmux -L environment-test new-session \
-d \
-s example

Inspect the global environment:

tmux -L environment-test show-environment -g APP_ENV

Expected result:

APP_ENV=global

Set a session-specific override:

tmux -L environment-test set-environment \
-t example \
APP_ENV session

Inspect the session entry:

tmux -L environment-test show-environment \
-t example \
APP_ENV

Expected result:

APP_ENV=session

Create a new window that writes its environment to a file:

tmux -L environment-test new-window \
-t example \
'printenv APP_ENV > /tmp/tmux-app-env'

Read the result:

cat /tmp/tmux-app-env

Expected result:

session

The session value overrode the global value.

Now remove the session-specific entry:

tmux -L environment-test set-environment \
-u \
-t example \
APP_ENV

Create another process:

tmux -L environment-test new-window \
-t example \
'printenv APP_ENV > /tmp/tmux-app-env-global'

Read it:

cat /tmp/tmux-app-env-global

Expected result:

global

Without the session override, the process inherited the global value.

Finally, destroy the experimental server:

tmux -L environment-test kill-server

Because the experiment uses its own named server, it does not require destroying ordinary tmux sessions on the default server.

The practical mental model​

When a value inside tmux is surprising, ask four separate questions.

What does the current shell contain?​

printenv APP_ENV

What does the tmux server contain globally?​

tmux show-environment -g APP_ENV

Does the session contain its own entry?​

tmux show-environment -t session-name APP_ENV

Was the pane created before or after the value changed?​

An existing shell retains the process environment it already inherited. A new pane or window causes tmux to construct a new initial environment.

These questions are more useful than asking simply:

What is the tmux environment?

There is no single environment shared continuously by the client, server, sessions, and running pane processes.

There is an inheritance path.

Conclusion​

A new tmux session is not necessarily an environmental reset.

The tmux server is long-lived. When it starts, it captures a global environment. Sessions can contain their own environment entries. When tmux creates a new window or pane process, it merges those stored environments, with session values taking precedence.

After that process begins, its environment belongs to the process. Changes to the shell do not automatically update tmux, and changes to tmux do not retroactively update the shell.

The essential inspection commands are:

# Current shell process
printenv APP_ENV

# tmux server's global environment
tmux show-environment -g APP_ENV

# A particular session's environment
tmux show-environment -t development APP_ENV

# Variables tmux is configured to update from clients
tmux show-options -g update-environment

The essential model is:

server startup environment
↓
tmux global environment
+
tmux session environment
↓
new pane or window process
↓
independent running process environment

Once those layers are visible, tmux environment behavior stops looking mysterious. The unexpected value is not appearing from nowhere. It is being inherited from a specific layer whose lifetime is longer than it first appears.

Comments

No comments yet. Be the first!