Skip to content

Commit 5cd3ccf

Browse files
openhands-agenttony
authored andcommitted
Fix race condition in new_session() by avoiding list-sessions query
Previously, new_session() would run 'tmux new-session -P -F#{session_id}' then immediately query 'tmux list-sessions' to fetch full session data. This created a race condition in PyInstaller + Python 3.13+ + Docker environments where list-sessions might not see the newly created session. The fix expands the -F format string to include all Obj fields, parsing the output directly into a Session object without a separate query. Co-authored-by: openhands <openhands@all-hands.dev>
1 parent e59d417 commit 5cd3ccf

2 files changed

Lines changed: 22 additions & 13 deletions

File tree

src/libtmux/neo.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,22 @@ def _refresh(
177177
setattr(self, k, v)
178178

179179

180+
def get_output_format() -> tuple[list[str], str]:
181+
"""Return field names and tmux format string for all Obj fields."""
182+
# Exclude 'server' - it's a Python object, not a tmux format variable
183+
formats = [f for f in Obj.__dataclass_fields__.keys() if f != "server"]
184+
tmux_formats = [f"#{{{f}}}{FORMAT_SEPARATOR}" for f in formats]
185+
return formats, "".join(tmux_formats)
186+
187+
188+
def parse_output(output: str) -> OutputRaw:
189+
"""Parse tmux output formatted with get_output_format() into a dict."""
190+
# Exclude 'server' - it's a Python object, not a tmux format variable
191+
formats = [f for f in Obj.__dataclass_fields__.keys() if f != "server"]
192+
formatter = dict(zip(formats, output.split(FORMAT_SEPARATOR), strict=False))
193+
return {k: v for k, v in formatter.items() if v}
194+
195+
180196
def fetch_objs(
181197
server: Server,
182198
list_cmd: ListCmd,

src/libtmux/server.py

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from libtmux.common import tmux_cmd
2020
from libtmux.constants import OptionScope
2121
from libtmux.hooks import HooksMixin
22-
from libtmux.neo import fetch_objs
22+
from libtmux.neo import fetch_objs, get_output_format, parse_output
2323
from libtmux.pane import Pane
2424
from libtmux.session import Session
2525
from libtmux.window import Window
@@ -539,9 +539,11 @@ def new_session(
539539
if env:
540540
del os.environ["TMUX"]
541541

542+
_fields, format_string = get_output_format()
543+
542544
tmux_args: tuple[str | int, ...] = (
543545
"-P",
544-
"-F#{session_id}", # output
546+
f"-F{format_string}",
545547
)
546548

547549
if session_name is not None:
@@ -580,18 +582,9 @@ def new_session(
580582
if env:
581583
os.environ["TMUX"] = env
582584

583-
session_formatters = dict(
584-
zip(
585-
["session_id"],
586-
session_stdout.split(formats.FORMAT_SEPARATOR),
587-
strict=False,
588-
),
589-
)
585+
session_data = parse_output(session_stdout)
590586

591-
return Session.from_session_id(
592-
server=self,
593-
session_id=session_formatters["session_id"],
594-
)
587+
return Session(server=self, **session_data)
595588

596589
#
597590
# Relations

0 commit comments

Comments
 (0)