Skip to content

show_option coerces user options, so an @ option set to 123 or on comes back as an int or a bool #759

Description

@isamu

Filed against tmux-python/libtmux v0.62.0, tmux 3.6a.

What happens

convert_value applies tmux's built-in option coercion to every value, and the call sites reach user options too:

src/libtmux/options.py#L245convert_values, dict branch
src/libtmux/options.py#L1216_show_option, direct lookup

tmux(1) defines a user option as carrying an arbitrary string:

tmux also supports user options which are prefixed with a '@'. User options may have any name, so long as they are prefixed with '@', and be set to any string.

So a user option does not survive the round trip when its value happens to read as a tmux boolean or a number:

set show_option returns type
123 123 int
on True bool
off False bool
true 'true' str
3.5 '3.5' str

show_options() returns the same converted values.

The coercion is right for built-in options — tmux really does use on/off there. For a @ name it is also not self-consistent: on converts but true does not, 123 converts but 3.5 does not.

Repro

import libtmux

server = libtmux.Server(socket_name="probe")
pane = server.new_session("probe").windows[0].panes[0]

for raw in ("123", "on", "off", "true", "3.5"):
    pane.cmd("set-option", "-p", "@probe", raw)
    got = pane.show_option("@probe")
    print(f"{raw!r:8} -> {got!r:8} {type(got).__name__}")
'123'    -> 123      int
'on'     -> True     bool
'off'    -> False    bool
'true'   -> 'true'   str
'3.5'    -> '3.5'    str

Why it bites

A @ option is the natural place to stamp an identity on a pane. When that identity is user-supplied, someone who names a thing 123, on or off gets back a value that no longer compares equal to what they set, so the pane cannot be found again.

Found while fixing awslabs/cli-agent-orchestrator, where it made a terminal named 123 unaddressable.

Note

The module docstring already treats these as their own category:

There are also custom user options, preceded with @, which exist are stored to Options.context.user_options as a dictionary.

Options.context.user_options does not appear anywhere else in the source, so that looks like an intent that was never implemented.

I have a patch that guards both call sites on the @ prefix and adds the round-trip case to test_custom_options. Built-in options keep converting (exit-unattachedFalse, history-limit2000). Happy to open it as a PR if you want it shaped that way — it changes behaviour for anyone relying on the current conversion, so it seemed like your call rather than mine.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions