Expose tmux spawn provenance: a typed libtmux.env (TmuxEnv, spawn_session, is_inside_tmux)
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Aptitud para principiantes
- 45/100
Línea de trabajo
Start by reading Server.from_env() in server.py and Pane.from_env() in pane.py to understand the existing environment parsing. Add the proposed libtmux.env public API with strict TMUX and TMUX_PANE handling, provenance fields, and session resolution. Done means the documented TmuxEnv, spawn_session(), and is_inside_tmux() behaviors are implemented without changing existing from_env() behavior.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Summary
libtmux can tell you where a process is now (the from_env() family resolves TMUX_PANE against the live server). It has no way to tell you where a process was spawned — the provenance tmux freezes into $TMUX at exec time. These are different facts, they can disagree, and today a caller who wants the second one has to hand-roll a parse of $TMUX that is easy to get wrong.
This proposes a small public module, libtmux.env, that types tmux's environment contract and exposes spawn provenance under its own name.
Background: what tmux actually exports, and when
tmux writes exactly two variables into the environment of every process it spawns inside a pane, and never revises either one afterwards.
TMUX is set in environ.c:277-282:
if (s != NULL)
idx = s->id;
else
idx = -1;
environ_set(env, "TMUX", 0, "%s,%ld,%d", socket_path, (long)getpid(), idx);
So TMUX is "<socket_path>,<server_pid>,<session_id>", where the session id is the bare integer (47, not $47 as libtmux spells it), and is -1 when the pane was spawned without a session.
TMUX_PANE is set in spawn.c:325:
environ_set(child, "TMUX_PANE", 0, "%%%u", new_wp->id);
Both are written once, at spawn. Grepping tmux 3.7b for every other reference to these names turns up only reads — cmd-find.c:93, server-client.c:228, tmux.c:491. tmux never reaches into a running child to update them.
The consequence: the session id in $TMUX is provenance, not location. Move the pane's window to another session (move-window) and that id is simply wrong about where the process is now. It may even name a session tmux has since destroyed.
Why libtmux should expose this, rather than leave it to callers
The from_env() family deliberately reads only the socket path out of $TMUX and resolves everything else from live tmux state — that is the right default, and it is why Session.from_env() can be trusted to answer "which session am I in now?".
But "which session launched me?" is a real, separate question, and there is no supported way to ask it. Callers who need it (a supervisor tagging work by originating session; an agent reporting where it was started; a tool that wants to detect that it has been moved) reach for os.environ["TMUX"] and parse it themselves. The naive parse is wrong in at least three ways:
split(",")instead ofrsplit(",", 2)— a socket path containing a comma is legal, and splits from the left into garbage.- No handling of
-1, so "spawned without a session" silently becomes the string"-1"and then a lookup failure. - No normalisation of the bare id to libtmux's
$-prefixed spelling, so it cannot be fed toSession.from_session_id()without a fixup the caller has to know about.
There is a fourth trap that is worse because it fails silently: TMUX_PANE must keep its % sigil. tmux's cmd_find_target routes a target by sigil (cmd-find.c:1078-1083 — $→session, @→window, %→pane), so a sigil-less "3" is interpreted as a pane index and cheerfully resolves to a different pane. A caller who strips or reconstructs the value gets a wrong answer with no error.
Every one of these is a parse libtmux already has to do internally. Making it public costs almost nothing and removes a footgun.
Proposed API
A new module, libtmux.env:
class TmuxEnv(t.NamedTuple):
"""tmux's environment contract, as frozen into this process at spawn."""
socket_path: str
"""Absolute path to the tmux server's socket."""
server_pid: int
"""PID of the tmux server process."""
spawn_session_id: str | None
"""Session this process was spawned in, e.g. ``"$47"``. ``None`` when tmux wrote ``-1``.
Provenance, not location. Makes no claim about the present: the session may since have been
renamed or killed, and it may no longer hold :attr:`pane_id`.
"""
pane_id: str
"""Pane this process runs in, e.g. ``"%3"``. Stable — a pane is never reparented into another pane."""
@classmethod
def from_env(cls, env: t.Mapping[str, str] | None = None) -> TmuxEnv: ...
@property
def server(self) -> Server:
"""Server named by the socket path. Runs no tmux command."""
def spawn_session(self) -> Session | None:
"""Session this process was spawned in, if it still exists.
``None`` when tmux wrote ``-1``, or when the spawn session has since been killed.
A server that is unreachable raises rather than collapsing to ``None`` — "server is gone"
and "session is gone" are different facts.
"""
def is_inside_tmux(env: t.Mapping[str, str] | None = None) -> bool:
"""True when this process runs inside a tmux pane."""
Design notes:
- Parsing is strict. A malformed
TMUX, or aTMUX_PANEwithout its%sigil, raisesNotInsideTmuxrather than being guessed at. Guessing here produces a silently wrong pane, which is the failure mode the module exists to prevent. spawn_session_idis a string id, not aSession. ASessionobject invites.windows/.active_paneand so looks like a location. A bare"$47"cannot be mistaken for one. Resolving it is opt-in viaspawn_session(), whose name still says "spawn" at the call site.str | None, not a sentinel. tmux's-1gets its own value in the type.- A module, not classmethods on
Server. These are not server facts. Hanging a frozen, possibly-stale value off the class whose whole job is live state would re-create the exact conflation this is meant to avoid. libtmux already splits concerns into small modules (neo,formats,constants,exc), andenvsits naturally beside them.
The parse itself is not net-new work — Server.from_env() and Pane.from_env() already need it. The only question is whether it stays private or becomes a supported surface.
Detecting that you have been moved
With the module in place, "am I still where I started?" is one honest line, and the caller can see what it costs:
env = TmuxEnv.from_env()
moved = env.spawn_session_id != Session.from_env().session_id
A dedicated is_stale() / moved() helper was considered and rejected: it hides three tmux round-trips behind a property that reads like an attribute.
Scope
Purely additive. No existing behaviour changes; from_env() keeps resolving through the live server and keeps ignoring the session field of $TMUX.
- Lenguaje dominante
- Python
- Estrellas
- 1.2k
- Forks
- 127
- Métricas de merge de PR
- Sin PR fusionados en 30 d
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de tmux-python/libtmux
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
tmux-python/libtmux#759 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
tmux-python/libtmux#745 · 2 comentarios ·
-
enhancement
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
tmux-python/libtmux#744 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
tmux-python/libtmux#731 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
tmux-python/libtmux#654 ·
Todos los issues de tmux-python/libtmux
Issues similares
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 90/100
Los mantenedores suelen responder en 1 día
-
https://search.utilibre.orgAbiertoinstance instance add
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
searxng/searx-instances#941 · 1 comentario ·
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 92/100
FluidNumerics/fluid-walk-blocker#89 ·
Los mantenedores suelen responder en 1 día
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
Los mantenedores suelen responder en 1 día