Skip to content

Add configurable shell-history suppression#91

Merged
tony merged 20 commits into
mainfrom
suppress-history
Jul 11, 2026
Merged

Add configurable shell-history suppression#91
tony merged 20 commits into
mainfrom
suppress-history

Conversation

@tony

@tony tony commented Jul 10, 2026

Copy link
Copy Markdown
Member

Summary

  • Add LIBTMUX_SUPPRESS_HISTORY=0|1 as a startup default for run_command only, with invalid values failing startup without reflecting configuration contents; keep suppress_persistent_history on create_session, create_window, split_window, and respawn_pane explicit per call and independent of that setting.
  • Publish the effective non-nullable suppress_history default for run_command through tools/list; explicit per-call values win, and direct Python calls remain default-off.
  • Apply best-effort history controls to spawned shells while preserving raw terminal input, paste behavior, and direct tmux launch strings.
  • Document shell-specific limits, spawn scope, audit behavior, and the remaining visibility surfaces without presenting history suppression as secret transport.

Changes

MCP policy and contract

  • Resolve the startup setting once for run_command and apply precedence as explicit call value, startup setting, then False; spawn tools never inherit it.
  • Use FastMCP's public tool transform so eligible MCP schemas expose the effective default without changing direct Python signatures.
  • Report the active policy in bounded server instructions while keeping raw send, batch, and paste behavior explicit.

Spawned shells

  • Copy and normalize caller environment input before adding an empty HISTFILE, merging Bash's HISTCONTROL, and enabling Fish private-history variables.
  • Reject conflicting history controls with value-free errors and never retry a failed call without suppression.
  • Store create_session controls in the tmux session environment for future panes; keep create_window, split_window, and respawn_pane controls scoped to the spawned process.
  • Leave direct shell and window_shell launch strings unchanged. An explicit false adds no controls but cannot remove controls already inherited from a session, parent process, or startup file.

Terminal safety

  • Prefix the grouped run_command event only when suppression is effective and reject caller-supplied carriage returns or line feeds before tmux receives input; explicit false preserves intentional multiline commands.
  • Keep send_keys and every send_keys_batch operation explicit-only so control keys, partial input, literal text, Enter boundaries, and TUI interaction retain their exact bytes.
  • Keep paste_text and paste_buffer outside the suppression policy.

Documentation and audit behavior

  • Describe Bash, Zsh, and Fish persistence and in-memory limits with version-pinned public sources.
  • Distinguish mapping-form environment audit summaries from JSON-string scalar redaction.
  • State that pane output, scrollback, capture, process inspection, client transcripts, hooks, and other logs remain observable.

Design decisions

  • Use startup configuration plus per-call overrides instead of a mutable global tool, avoiding cross-client policy races.
  • Apply the global default only to semantic run_command calls; spawned-shell controls stay explicit per call, and arbitrary terminal input cannot safely inherit a byte prefix.
  • Treat suppression as best effort because shell startup files and user hooks can override history behavior after spawn.

Test plan

  • Cover startup parsing, schema defaults, explicit overrides, direct and generic batch calls, and instruction budgeting.
  • Prove raw control keys, literal and TUI input, timed and untimed batches, Enter boundaries, and paste paths remain unchanged.
  • Exercise adversarial multiline rejection before tmux access and preserve explicit suppression-off multiline behavior.
  • Cover environment copying, normalization, merging, conflict confidentiality, spawn scope, direct launch strings, and inherited-control behavior.
  • Verify Bash, Zsh, and Fish mechanism application and persistence behavior with explicit skip reasons when a shell is unavailable.
  • Contract-test configuration, tool pages, safety guidance, audit shapes, and the unreleased changelog.

Verification

  • Ruff formatting and linting
  • Strict mypy
  • Full pytest suite
  • Sphinx documentation build
  • Production FastMCP inspection with suppression disabled and enabled
  • Real FastMCP client and isolated tmux smoke
  • Supported tmux matrix from 3.2a through master

Closes #90.

why: Let semantic MCP commands inherit a strict startup history default
without changing raw terminal input or direct Python call behavior.

what:
- Resolve and publish LIBTMUX_SUPPRESS_HISTORY for run_command
- Keep raw send, batch, and paste input explicit-only
- Add MCP, Bash history, instruction-budget, and boundary regression tests
@codecov-commenter

codecov-commenter commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.71429% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.20%. Comparing base (e9fa03d) to head (0c83e18).

Files with missing lines Patch % Lines
src/libtmux_mcp/server.py 84.21% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #91      +/-   ##
==========================================
+ Coverage   85.00%   85.20%   +0.19%     
==========================================
  Files          43       44       +1     
  Lines        3202     3265      +63     
  Branches      439      452      +13     
==========================================
+ Hits         2722     2782      +60     
- Misses        349      352       +3     
  Partials      131      131              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

tony added 8 commits July 9, 2026 20:04
why: Let MCP-spawned shells inherit the configured history policy
without rewriting launch commands or mutating caller environments.

what:
- Normalize and merge controls across all four spawn tools
- Preserve positional compatibility and tmux environment scope
- Cover schemas, conflicts, forwarding, and Bash/Zsh/Fish behavior
why: MCP clients need startup precedence in tool schemas and accurate,
value-free guidance when a history setting conflicts.

what:
- Explain MCP omission, explicit override, and Python defaults
- Give shell-specific guidance without reflecting supplied values
- Cover transformed schemas and every conflict surface
why: Give operators an accurate user-facing contract for best-effort
history controls and the visibility surfaces they do not hide.

what:
- Document startup precedence, eligible tools, shell caveats, and scope
- Add safety and logging boundaries for remaining visibility
- Lock stable documentation claims with focused contract tests
why: Server instructions named only run_command even though semantic spawn
commands inherit the active shell-history default.

what:
- Name run_command and spawn inheritance in the active-default sentence
- Preserve raw input exclusions and UTF-8 instruction-budget guards
why: Keep the process-environment coverage compatible with tmux 3.2a's
minimum pane size.

what:
- Reuse the verified later pane for the respawn assertions
- Avoid creating a fifth pane in the fixture window
why: Describe both accepted respawn environment representations and
their distinct audit summaries.

what:
- Explain per-key redaction for mapping input
- Explain scalar redaction for JSON object strings
why: Prevent caller line breaks from escaping the grouped history event
when suppression is enabled.

what:
- Reject carriage returns and line feeds before tmux resolution
- Preserve explicit suppression-off multiline behavior
- Document and test the narrowed contract
why: Keep public history and audit claims consistent with the accepted
input shapes and source-link policy.

what:
- Distinguish mapping and JSON-string audit summaries
- Link Bash behavior through a version-tagged GitHub mirror
- Add documentation contract coverage
@tony tony marked this pull request as ready for review July 10, 2026 03:18
tony added 9 commits July 10, 2026 17:05
why: Semantic commands should avoid shell-history persistence by default
while raw input and process spawning remain explicit caller choices.

what:
- Treat an unset suppression setting as enabled
- Transform only the run_command MCP schema default
- Document and test command-only inheritance boundaries
why: Distinguish lightweight command-entry suppression from explicit
persistent-history controls for newly spawned shells.

what:
- Rename the spawn-only option to suppress_persistent_history
- Keep startup history defaults scoped to semantic commands
- Preserve validated environment merging and tmux spawn behavior
why: Distinguish default-on command suppression from explicit
spawn-time persistent-history controls.

what:
- Document command precedence and the multiline opt-out
- Explain spawn scope, shell caveats, and visibility boundaries
- Lock the public history contract with documentation tests
why: Keep spawn and discovery wording accurate after the default
flip.

what:
- Distinguish command text from tmux environment arguments
- Explain Python table and MCP discovery defaults
- Lock the clarified wording in documentation tests
why: Keep the public run-and-wait prompt executable when a command has
line breaks and the MCP history-suppression default is enabled.

what:
- Render an explicit suppression opt-out for LF and CR commands
- Disclose possible shell-history persistence in multiline recipes
- Cover real and escaped line breaks without changing single-line output
why: Spawn environment values cross process-observable surfaces even when
the MCP audit record redacts them.

what:
- Warn create_window and split_window callers in their MCP schema text
- Explain tmux argv, child environment, and session-state boundaries
- Contract-test client-visible descriptions and central safety guidance
why: Session environment values persist beyond launch and remain visible
outside the redacted MCP audit record.

what:
- Add client-visible argv, session-state, and child-environment guidance
- Add an early credential warning to the create-session task page
- Contract-test the schema disclosure and task-page placement
why: Repeated safety icons fragment dense explanatory sequences and conflict
with the documented role convention.

what:
- Use toolref for the three reviewed dense tool sequences
- Bind role contracts to the exact paragraphs and safety bullet
why: The changelog contract should validate each product feature
without coupling assertions to unrelated unreleased entries.

what:
- Scope history-control assertions to their deliverable blocks
- Cover the environment summary and linked safety guidance
@tony tony force-pushed the suppress-history branch from 0518fcc to e542067 Compare July 11, 2026 00:47
tony added 2 commits July 11, 2026 04:59
why: Give end users one path from default-on command history hygiene
to stronger opt-in spawned-shell controls for Bash, Zsh, and Fish.

what:
- Add a dedicated topic and front-page workflow
- Document multiline prompts and scoped spawn environments
- Link affected tools and result models at point of use
- Move the docs contract to the canonical topic
why: The unreleased changelog should describe the branch in
user-facing terms without release ceremony or implementation detail.

what:
- Separate compatibility, spawned-shell, and environment capabilities
- Link migration and safety detail to focused topic guidance
@tony tony force-pushed the suppress-history branch from e542067 to 0c83e18 Compare July 11, 2026 10:01
@tony tony merged commit 7f0074e into main Jul 11, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

History hygiene for shell commands and spawned shells (spike)

2 participants