tmux-python / tmux-python/libtmux-mcp
The documented self-location recipe is unexecutable: filters={"is_caller": true} silently returns []
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 13
- Forks
- 0
- Avg merge
- 13h 52m
- Merged PRs (30d)
- 4
Description
Summary
The server instructions tell every agent, verbatim, how to answer "which pane am I in?":
Tool results mark is_caller=true; filter list_panes for it to answer 'which pane am I in?' (no whoami tool).
That instruction cannot work. _apply_filters applies filters to the raw libtmux QueryList of Pane objects before serialization, but is_caller is a serialization-time computation — it does not exist as an attribute on libtmux.Pane. libtmux's keygetter returns None for the unknown attribute and filter_lookup then returns False for every row, so the call returns an empty list.
It fails silently: no error, no warning, just []. That is the worst possible failure shape for an LLM — the agent concludes "I'm not in any pane" or, more commonly, falls back to pulling the full unbounded listing and post-filtering in its own context. Which is precisely the expensive path the instruction appears to be steering it away from.
Evidence
The instruction, in _BASE_INSTRUCTIONS:
". Tool results mark is_caller=true; filter list_panes for it to answer "
"'which pane am I in?' (no whoami tool)."
Filters are applied to un-serialized libtmux objects:
src/libtmux_mcp/_utils.py#L841—_apply_filterscallsitems.filter(**coerced)on theQueryList, then serializes the survivors.
But is_caller is only produced during serialization:
src/libtmux_mcp/_utils.py#L977—is_caller=_compute_is_caller(pane)inside_serialize_panesrc/libtmux_mcp/_utils.py#L153—_compute_is_caller
And the lookup silently degrades to False rather than raising:
libtmux/_internal/query_list.py#L535—keygetterreturnsNonefor an unknown attribute;filter_lookupthen evaluatesFalsefor every pane.
Reproduction
Environment: libtmux-mcp==0.1.0a17, MCP server launched from inside a tmux pane on the default socket.
- Call
list_panes(filters={"is_caller": true}). - Observed:
[]— an empty list, no error. - Expected: exactly one
PaneInfo(the caller's own pane); or, failing that, an explicit error stating thatis_calleris not a filterable field. - Control: call
list_panes()with no filters and observe that exactly one row does carryis_caller: true. The field is real; it just cannot be filtered on.
Suggested fix
Two independent changes; both are worth making.
1. Make the advertised filter actually work. is_caller is a synthetic, serialization-time field, so it must be filtered after serialization. Split the filter set in _apply_filters:
_SYNTHETIC_PANE_FIELDS = {"is_caller"}
def _apply_filters(items, filters, serializer):
coerced = _coerce_dict_arg("filters", filters)
if not coerced:
return [serializer(item) for item in items]
synthetic = {k: v for k, v in coerced.items() if k in _SYNTHETIC_PANE_FIELDS}
native = {k: v for k, v in coerced.items() if k not in _SYNTHETIC_PANE_FIELDS}
rows = [serializer(item) for item in (items.filter(**native) if native else items)]
for key, want in synthetic.items():
rows = [r for r in rows if getattr(r, key, None) == want]
return rows
Alternatively, reject unknown filter keys with an ExpectedToolError rather than returning [] — a silent empty result is strictly worse than a loud failure.
2. Stop routing self-location through a full listing at all. Even a working filters={"is_caller": true} still pays a full list-panes -a server-side; it only saves tokens, not tmux work (_apply_filters materializes the whole QueryList and filters in Python). The right primitive is a dedicated self-location tool (where_am_i) — see the companion issue. Once it exists, delete the filter list_panes for it sentence from _BASE_INSTRUCTIONS entirely.
Tests
In tests/test_pane_tools.py (or tests/test_utils.py):
- Monkeypatch
_get_caller_identityto return a deterministicCallerIdentitypointing at a known pane on the test server. - Assert
list_panes(filters={"is_caller": True})returns exactly that one pane — this fails today, returning[]. - Assert
list_panes()(unfiltered) contains exactly one row withis_caller is True, proving the field is populated and the bug is in the filter path only. - Add a regression test that an unknown/synthetic filter key does not silently yield
[].
Related
- #19 —
is_callerannotation gives false positives across tmux sockets (same annotation, adjacent defect) - Companion issue: add a
whoamiprimitive so the instructions no longer have to advertise this recipe at all.
Environment
- libtmux-mcp: 0.1.0a17
- libtmux: 0.61.0
- fastmcp: 3.4.3 / mcp: 1.28.1
- tmux: 3.7b
- Python: 3.14.0
Cross-references — filed together from a single audit of v0.1.0a17. The self-location gap is the root cause; the rest are what it exposed.
- tmux-python/libtmux-mcp#92 — [bug] documented self-location recipe is unexecutable (
filters={"is_caller": true}→[]) - tmux-python/libtmux-mcp#93 — [feat]
where_am_iself-location primitive + self-first relational routing (root fix) - tmux-python/libtmux-mcp#94 — [bug] caller's socket advertised but never targeted;
TMUXenv race vs. the threadpool - tmux-python/libtmux-mcp#95 — [perf]
list_*unbounded, unscoped, unprojected, excluded from the response limiter - tmux-python/libtmux-mcp#96 — [feat] invocation context (in tmux? server running?) + explicit tmux-server auto-start
- tmux-python/libtmux#704 — upstream: no self-location API (
TMUX_PANE); unreachablesocket_pathderivation
(← this issue: #92)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with _apply_filters in src/libtmux_mcp/_utils.py, then read _serialize_pane and _BASE_INSTRUCTIONS in src/libtmux_mcp/server.py. Reproduce the empty result with list_panes(filters={"is_caller": true}), then run the relevant tests in tests/test_pane_tools.py or tests/test_utils.py. Done means synthetic filtering works and unknown filter keys no longer silently return an empty list.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend-api-design, testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100