Be Careful Using Tmux and Environment Variables
aj.codes
aj.codes
When you start a screen session, it will always copy the environment from your shell. When you start a second screen session, it will inherit a possibly different environment.
But tmux, on the other hand, spawns a tmux server when you first start a session. It'll copy the environment from your shell. However, from that point onwards, new sessions will use the environment from the server and will not copy the environment from your shell.
I like screen's behavior more. A frequent use case is to start a long-running command as a sort of ad-hoc background job. That's really easy with screen (just run screen and run your command, it will always use the parent shell's environment) but relatively easy to screw up with the environment when done with tmux. It kind of works only if you don't have a tmux server already running.
I use a nested setup with screen where each level of nesting has a different escape key, this works really well when I want an inner screen to be on a different machine.
screen '-e^Zz' -S meta
and then inside that one screen '-e^Oo' -S project1
and for a session on a remote machine mosh some_server -- screen '-e^Oo' -S remote_session_name
I haven't figured out a way to get this to work well with tmux, because the local tmux server doesn't know about the remote tmux sessions. Whereas with screen, screen doesn't need to know or care about that.(edit: I use shell aliases, I don't type out the '-e^Oo' etc every time)
That particular feature is pretty important to me, and it would suck if it got removed. Just because a codebase is ancient doesn't mean it's useless.
why does the local tmux server need to know about the remote tmux session?
local computer with screen
\
\ ssh/mosh to remote A, run screen
\
\ ssh/mosh to remote B, run screen
If each uses the same prefix (the default is C-a for screen, C-b for tmux) then you have to send it multiple times to send a command to the screen sessions on either of the remote systems. If you give each a unique prefix then you can send the prefix once and it goes directly to the desired screen session.Easier to give each system its own prefix that's constant regardless of depth, though, than trying to remember "Did I connect to A first or B first today?". Or, better in my experience, don't nest them. It's a pain however you choose to deal with the prefix problem.
I started using this pattern before I moved to macos, but now that I’m usually on a mac, it helps because I still haven’t gotten used to macos window / desktop management. Managing one terminal is easier than a half dozen.
But... isn't tmux sessions exactly what you'd need for that? One session per project, then just connect to servers from within tmux windows
There is even switch-tree (default C-b w) that will show each session with every sub-window in it, and even preview window below that showing each terminal (refreshable via space)
It's odd. A couple of days ago I watched Tristam Oaten's No Boilerplate video "Oxidise Your Life" [1] and it left me thinking about how Rust is moving some programmers to rewrite tools in Rust and, while at it, adding modern features. It's nice to see that.
Zellij has Lock interface, default Ctrl+G, which you can use and in that mode it only takes that one key, but it's a bit different from how screen works.
That said, I must admit that zellij doesn't add a whole lot to my workflow relative to tmux. I think I'm just easily distracted by the new-and-shiny.
FFS. Can't we, at some point, be done with wheel re-invention?
You do realize how much of technology and coding is literally re-writing things constantly until they work for [insert stakeholder here]. Right?
C-b s gets me graphical(via rofi + some scripting) searchable list of windows, in rare cases where I'd need tens of them.
But it took me a while to make tmux keybindings that are fast & convenient, screen defaults (aside from C-a that collides with bash/emacs) on keybindings are way better.
I'm missing something here. Why does tmux need to know or care?
Using c-bb to get to the nested one doesn't seem that bad to me as long as it's only 2-deep.
But why not just change the escape key on each machine's .tmux.conf?
I think tmux's choice is actually the sane one. You create the server once, then there's no more question about what environment variables are going to be established in the future.
When you create a new tmux window, you should know if there's already a server spawned.
I'm a big fan of abstracting away details of little importance. But if I risk using an old server with stale values in its environment, I prefer that to be made explicit and not abstracted away.
I'm pretty happy with this behaviour and being able to tweak it if I need more.
i have tmux running all the time. started once when i connect to a server for the first time, it keeps running until the server is rebooted. i have tmux sessions that go back years.
the only time the environment is causing me trouble is when a new ssh connection is forwarding an ssh agent, which doesn't get transferred into the tmux session when i attach. but screen would not help here either. if you connect to an existing screen session it has the same problem. we really want a way to adjust the environment for specific variables on attach.
I don't really agree. When I start a program in a shell, I expect it to inherit the environment of that shell. That's just how things have always worked, modulo some special cases (sudo, running a new instance of the shell in login mode, whatever).
I personally don't think that's weird, but even if I did, what I care most about software behavior is predictability. Software should do what is expected and common, and if it does not, it should have a very good reason, and should find a way to make that obvious to the user every time it does it.
This is just bad UX on tmux's part.
> When you create a new tmux window, you should know if there's already a server spawned.
What? Why? I use screen on and off, not super often. When I start a screen instance, I don't always know or care if I have another instance running somewhere. And yes, because screen follows what I'd consider a more predictable model here, I end up with the environment variables I expect.
I think whenever we attack a UX problem by saying "the user should know X", we've already lost. You can't assume that you know how the user is going to use your software, or what their state of mind will be when starting it. That's just silly.
That's exactly how tmux works. You start a server, it inherits the environment of the current shell. You create a new session in that server, it inherits the environment of the server.
I don't understand how forcing tmux to re-load the current local environment for each new shell is saner behavior. One natural consequence would be that new panes in existing sessions are also created with the new env vars, which is obviously undesirable.
> That's exactly how tmux works. You start a server, it inherits the environment of the current shell. You create a new session in that server, it inherits the environment of the server.
Whether that makes sense depends on if your mental model has starting a server and connecting to a server to create a new shell as distinct actions, as opposed to a singular action of connecting to a conceptually always-present persistent environment and starting a shell there. Admittedly tmux’s interface is mostly organized along the former lines, but given I prefer `tmux new -A`, my own model seems to follow the latter.
It's kind of interesting reading through the threads here and seeing how people don't spend their time inside tmux all the time but instead "start an instance on demand" - which I've never done but I guess works for some people's workflow?
I'm genuinely crippled trying to use a terminal without tmux after having used it for so long - don't have the faintest clue how to copy/paste/search/organize windows without it.
I think the tmux sentiment in question is held by people new to the tool or that don't use it as a normal part of their workflow or day-to-day.
I spent 2+ months in that trailer for 6-8 hours/day - and tmux became so embedded in my finger DNA, I never looked back.
I routinely bounce between WSL, Linux, and MacOS and never have to remember any of the keyboard shortcuts for the terminal programs, or whether it's been installed, and my fingers never leave the keyboard - and I can almost always outperform someone trying to select/copy with a mouse/touch pad. My .tmux.conf is the only thing I pull along with me.
Also - just thinking about it - tmux is more than just a windowing environment - it also does project management. I have a workflow where every new project/issue/incident gets its own named window - and then I break up that window into a set of 4-8 panes based on systems (or sets of systems) I'll be working on. They all share the same bash_history. As I get hit with interrupts, I bounce between sessions and when I come back - my whole thought process/work history is back in that named window with panes. It's not unusual for me to have 45-50 open panes at a time - all perfectly organized by project/named window. With the resurrect plugin, and intelligent use of my bash config - I can (and do) pull everything (scroll back history, bash history, etc..) onto another system if needed (great if you need to recreate your work history and you no longer have access to the original terminal environment).
As I close off/complete projects, I terminate the window - but the bash history is kept clean in ~/.tmux/bash_history/window_name in case I ever need to start working on it again (recreating the window with the same name pulls back in the bash history)
Introspecting here a little, this honestly is probably more a function of me spending 40+ hours in a terminal environment than anything a rationale casual user would ever want to do.
tmux serves the same purpose as DE, except w/o the whole windowing system etc. It's to allow you to switch between multiple applications which require terminal for interaction.
I spend close to 100% of my time at work inside tmux, and I never needed to answer a question "where do the process variables come from?" -- it's irrelevant in the same way how you don't ask where does Gnome get its environment variables from. You simply don't rely on environmental variables when running tmux / don't care about them.
The heap issues kept occurring even though I triple checked the ENV variables. However, ps -ef showed the old values!
Once I figured out he was using tmux the issue was resolved by closing out all sessions and starting new ones.
Intuitively, it makes sense to be able to start a tmux session after setting up some global (i.e. session wide) environment variables, so that all new windows spawned in that session inherit them. Once you've specified the database or various urls, you don't want to have to source some environment file each time you spawn a window. The session should just already know all this.
Inheriting some super global variables (i.e. server wide, machine wide) by default (as opposed to explicitly setting a flag) implies that a shared environment across sessions is more useful than a shared environment across windows within the same session. It's a matter of opinion, taste, or maybe of UX data points, but I suspect that the latter would be favored if there ever was a survey.
I'm wondering if sessions were more relevant before we all had 30+" monitors on our desk, and panes were less useful. Or perhaps some people have, as you noted, more well defined work/project paradigms that they can associate with a session.
With that said - in my day job, we have a really well engineered set of tooling that lets us manage our certificates, ssh keys, and orchestraor/security engine/service discovery tokens - but I'm managing and updating dozens of environments/configs - honestly the environment that I opened even a single pane (inside a window) might shift several times over 5-10 minutes into entirely different clusters - what that pane started with is mostly irrelevant - (and clearly the windows/session and "Tmux Server" environment that I started with is so irrelevant as to be meaningless).
About the only global original configurations that I care about and want to carry into my work are bound to my client (/etc/hosts, /etc/resolv.conf, interface, VPN, etc...)
The one thing that I've gotten out of this thread is that I probably should be more empathic to other people's workflow - not everyone has the same datacenter/cluster/server environments that I do - and instead of having tooling to manage their environment, they might just rely on what it was when they were launching a "new tmux session at that point in time" (a concept I had never considered until today).
I’m curious what was your case that triggered you to dig this up.
script envname command
then just loads .env.envname before executing.For k8s work I just put some relevant variables in PS1 so it is always visible "where I am", with prompt looking like this for say "dev" env:
[08:15:57] ^ [~]
k8s:dev -> ᛯ
As for tmux behaviour, it's honestly entirely understandable. tmux have no way of knowing the intent of the user; some people might want "clean" session with defaults of what server is running, other might want some env variables copied.I don't have that problem because I just have session being created in my WM autostart, and I just use that one session 99.99% of the time. And creating new ones usually is from tmux, not from some random shell window elsewhere
If you start server off your graphic session then any new tmux session will get those variables and your ssh agent or graphical app will "just work".
Reading from current one by default might fuck something up if you say have some automation that starts stuff in tmux
There isn't really good way to handle it by default that would make everyone happy, screen had similar "problems".
I just have
tmux new-session -d -s main
in my .Xsessionrc so the main session gets both SSH_AGENT stuff (I use gnupg as agent, for smartcard support) and proper DISPLAY, then just use alias to use that main sessionEnvironment vars are great with defaults for configuring codebases. They are designed for runtime. Build time flags can read the current environment but if your build pipeline requires custom variables you need a “dictionary” of sensible defaults that will spin up a local environment so that onboarding is as easy as git clone && make dev.
Alternately, set those vars in your shell RC and it'll work too when the shell is spawned.
IME it's stabler to kick off automation or build processes from a clean slate anyways, clearing the environment and setting specific values as required from a file.
Its my daily driver, but that means when I am in bare zsh, without tmux, it usually doesn't "work" for me. So, I can understand if someone is just spinning up tmux occasionally (to keep some long running program from stopping) that it can be a bit jarring. In some senses, the simpler 'screen' might actually be a better fit for that.
But yea, using tmux every day for years you become very thankful of how it operates, keeps variables isolated, you tend to work in one session per project (and it's great for not mixing them up).
But yea, it wouldn't be great if you are working away on something, then half way through just "start a session".
Tmux isn't going to do what the op is trying to do I suspect.
I'm a big tmux fan, it is awesome (in case that wasn't clear)
Thanks anyway for a good write up
That said, I can understand why it would work this way for simplicity, if all sessions are hosted inside the same process.
Luckily for me, I never use more than one session at a time, so this doesn't affect me.
Your first spawn of tmux starts the tmux server, all your sessions will connect to this server
But if it is only one session you are using, w
> [..]
> -WINDOWIDkj
Consequence of using Vim to edit blog posts? It should be, according to author's dotfiles :-P
Or maybe using tmux, because I also press kj to determine what pane I'm on sometimes.
correct me if im wrong but it seems like i can just do `tmux new -L dev` and `tmux attach -L dev` which is actually better than my old muscle memory. its a win-win for me
edit: actually jk it doesnt work.
meh who cares i always put my env vars in ~/.profile its weird how OP doesnt mention that at all
edit: damn, now i have to write a custom script for `tmux list-sessions`
#!/bin/bash
for L in $(ls /private/tmp/tmux-501); do
echo $L
tmux -L $L list-sessions
done
honestly at this point it isnt even worth it. i dont wanna have an extra script like this. ill just put my env vars in ~/.profileMy company provides me with the laptop, VPN connection to the office network, which, in turn, connects to our (tiny) datacenter, where actual work happens (i.e. compilation, CI, testing, all happen there).
Sometimes I work from the office, other times from home.
The datacenter has several "jump" servers having distinct roles, there is a server that lets you connect to our OpenStack cluster, another has some resources to run a bunch of unrelated VMs in KVM, yet another one is the storage for all kinds of artifacts, s.a. Linux packages, Linux distro images, Docker images for development and testing, and then there's CI cluster.
So, my typical setup is like this: I have an ansi-term buffer per jump server. The jump servers are running my tmux session. So, every time I have to move my laptop to a different network (eg. going home from office), I reconnect to the jump servers and to the tmux session they were running so that I can pick up from where I left off before disconnecting from office VPN.
If TRAMP could have a persistent session, maybe, I'd not use tmux (I don't like that I have different commands for managing buffer appearance and clipboard). On the other hand, tmux is more universally used (at least in my company), so that sometimes I can simply ask a colleague to connect to my session, if they need to investigate some strange situation (only a handful of people are using Emacs, but almost everyone would be able to use tmux at where I work).
screen -d [optionally] pid
Will detach screen from within screen. screen -x [optionally] pid
Will reattached with simultaneous attachment. screen -r [optionally] pid
Will detach (if needed) and reattach the session. -d|-D [pid.tty.host]
does not start screen, but detaches the elsewhere running screen session. It has the same effect as typing "C-a d" from screen's controlling terminal. -D is the equivalent to the power
detach key. If no session can be detached, this option is ignored. In combination with the -r/-R option more powerful effects can be achieved:
-d -r Reattach a session and if necessary detach it first.
-d -R Reattach a session and if necessary detach or even create it first.
-d -RR Reattach a session and if necessary detach or create it. Use the first session if more than one session is available.
-D -r Reattach a session. If necessary detach and logout remotely first.
-D -R Attach here and now. In detail this means: If a session is running, then reattach. If necessary detach and logout remotely first. If it was not running create it and notify the user.
This is the author's favorite.
-D -RR Attach here and now. Whatever that means, just do it.
Note: It is always a good idea to check the status of your sessions by means of "screen -list".The env vars from the shell where most recent attach happens are inherited when creating a new pane or window. This is the case even when multiple terminals are attached to the same session.
I'm on version 3.3a for reference.
In tmux's case, it starts new shells with "old" env vars, where "old" is "whatever was present when the first tmux session was started".
So it is roughly analogous:
VSCode ~ tmux server
VSCode shell ~ tmux pane
This is all expected and ergonomic I agree.
It's because my ssh session was bringing variables from my laptop onto the pi:
https://stackoverflow.com/questions/2499794/how-to-fix-a-loc...
I have a very quirky setup with tmux and rely on this feature, but it's broken IIRC and add important variables to update-environment manually.
set-option -g update-environment "*"Anyways, I'm Not a tmux user but it seems like a nice default behavior to have env. variables persisted
> to re-attach to the second session you’ll need to do tmux -L other attach -t second.
I have tmux running on a machine in my closet that I ssh to from a couple of different laptops. I name each session, and then always connect to the same session when I ssh into the closet machine.
tmux a -d -t session-name
The -d makes the sessions resize to match the current scrren size which is useful for me because I use different screen resolutions on both laptops.- create first session (launch server)
- detach
- set environmental variables
- create second session
But I don't understand why you would want to follow this workflow in practice.
When using tmux, all my work on that machine is done within tmux sessions, and I expect them to be sandboxed relative to each other. If I want to set an variable that's relevant to a session, I do it from within the session, not outside.
Ultimately we decided to use a named tmux socket to ensure the environment variables were picked up correctly when set via the make task but you can also use `set-environment` as well if the ergonomics hit of having to use the named socket each time is too much. It's nice that tmux provides a few work arounds but I thought the original behavior was not intuitive.
It's one of those rare gotchas that will bite someone in the ass. And it will hurt. And when it does, you'll swear like a mother f'er.
Other posters are right that the "correct" workflow is to set all your environment variables explicitly within the session before launching whatever process you're running, but on an ad-hoc basis, I've often found myself logged into a server and thinking "this may take a long time, I should run this in a tmux session," and this kind of behavior would have (and probably has) bitten me in that case.