[FEATURE] Add Interactive Shell Support to the Bash Tool via Pseudo-Terminal (PTY)

Status Open
Maintainer reply None cached
Activity 14 comments · opened Oct 19, 2025

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

Currently, the Claude Code Bash tool is incredibly powerful for executing non-interactive, stateless commands (ls, grep, cat, etc.). However, its capabilities are limited when a command requires real-time user interaction.

If a user asks Claude to run a command that spawns an interactive session—such as vim file.js, git rebase -i HEAD~3, npm init, or an interactive REPL like python—the Claude Code session will hang, wait for a timeout, or fail.

This forces the user to break their workflow:

  1. Exit or switch away from the Claude Code session.
  2. Open a separate terminal to run the interactive command.
  3. Complete the task.
  4. Return to Claude Code and manually provide context about what just happened.

This process is inefficient and, more importantly, it removes the interactive task from Claude's context. Claude has no awareness of the changes made in vim or the decisions made during a git rebase, limiting its ability to provide continuous, context-aware assistance.

Proposed Solution

I propose enhancing the Bash tool to support fully interactive shell commands by integrating a pseudo-terminal (PTY). This would allow Claude Code to spawn and manage interactive subprocesses, rendering their UI directly within the Claude Code REPL.

Ideal User Experience:

  1. A user prompts Claude: > open src/app.ts in vim so I can make a quick edit.
  2. Claude executes the tool call: Bash(command="vim src/app.ts").
  3. The Claude Code REPL interface is temporarily replaced by a live, interactive vim session. The user sees the file content and can edit it using standard vim commands.
  4. To ensure user input is correctly routed, a keybinding (e.g., Ctrl+F as used in Gemini CLI) could be used to "focus" the interactive terminal session. This would direct all subsequent keystrokes to the vim process instead of the Claude Code prompt.
  5. When the user saves and exits vim (e.g., with :wq), the interactive session terminates.
  6. The user is returned to the standard Claude Code prompt.
  7. Claude receives a ToolResult from the Bash tool indicating that the command completed successfully (e.g., exitCode: 0). Claude is now aware the file was edited and can proceed with the next step, like running tests.

This approach would leverage a library like node-pty to serialize the terminal state (text, colors, cursor position) and stream it to the user, creating a seamless, two-way interactive experience.

Alternative Solutions

  • Current Workaround: The only current workaround is to perform interactive tasks in a separate terminal. The major drawback is the complete loss of context for Claude, which defeats the purpose of an integrated agentic workflow.
  • Agent-Only Edits: One could argue that users should instruct Claude to perform all edits programmatically (e.g., using the Edit tool or sed). While viable for simple changes, this is clunky and inefficient for complex refactoring, multi-line edits, or tasks that are simply faster with a real editor. It also fails to solve the problem for other interactive tools like git rebase -i, htop, or setup scripts.

Priority

Critical - Blocking my work

Feature Category

CLI commands and flags

Use Case Example

A developer is working on a new feature and has made several small, messy commits. They want to clean up their git history before opening a pull request.

  1. User Prompt: > I need to clean up my last 3 commits. Start an interactive git rebase for me.
  2. Claude Action: Claude understands the request and executes Bash(command="git rebase -i HEAD~3").
  3. Interactive Session: The Claude Code REPL transitions into the editor defined by the user's git configuration (e.g., vim or nano). It displays the list of commits for the rebase.
  4. User Interaction: The user directly edits the text, changing pick to squash for two of the commits. They save and exit the editor.
  5. Context Preservation: The user is returned to the Claude Code prompt. Claude receives the output from the git command (e.g., "Successfully rebased and updated HEAD.").
  6. Next Step: The user can now say, > Great. Now push the changes to a new branch and create a PR. Claude has the full context that the rebase occurred and can proceed correctly.

Additional Context

  • Precedent: This feature was recently introduced in the Google Gemini CLI (v0.9.0) and has proven to be a major enhancement for their shell integration. Their implementation demonstrates the technical feasibility and value of this approach.
  • Reference: Google Developers Blog Post on Interactive Shell
  • Technical Considerations:
  • This would likely require the node-pty library or a similar PTY solution.
  • The terminal renderer would need to be updated to handle complex terminal UI escape codes for color and cursor positioning.
  • A clear mechanism for focusing/unfocusing the interactive session is crucial to manage user input.
  • This feature should integrate with Claude Code's existing permission system. A request to run vim would still be a Bash tool call that requires user approval, but once approved, the session becomes fully interactive.

View original on GitHub ↗

14 Comments

github-actions[bot] · 10 months ago

---

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/292
  2. https://github.com/anthropics/claude-code/issues/9587
  3. https://github.com/anthropics/claude-code/issues/9137

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

blocwave · 9 months ago

Second this. Would also love to have a PTY implementation of plan mode that allows dynamic commenting/editing of plans.

github-actions[bot] · 8 months ago

This issue has been inactive for 30 days. If the issue is still occurring, please comment to let us know. Otherwise, this issue will be automatically closed in 30 days for housekeeping purposes.

noahgoblair · 8 months ago

I would love this. When I run into interactive prompts I have to go run them in a separate terminal and that means Claude doesn't have the context of what happened without manual copy/paste of specific info which can be tedious at times.

I could get by with bash mode just staying on until I dismiss it so Claude gets the context. That would be an easy win.

rz1989s · 7 months ago

Hey team, adding another real-world use case that might help prioritize this.

Command: gh pr checks <PR> --watch --fail-fast

What happens: Each refresh cycle prints new lines instead of updating in-place, causing a "bleeding" effect where the terminal fills up with repeated output blocks.

Expected: The --watch flag should refresh in-place using cursor control sequences.

This is a pretty common workflow when waiting for CI checks to complete. Totally understand this requires proper PTY integration which isn't trivial - just wanted to add this as a concrete example of the impact.

Environment:

  • Claude Code: v2.1.4
  • Terminal: WezTerm + tmux
  • OS: macOS Darwin 24.6.0

Appreciate all the work on Claude Code - it's been a game changer for my workflow despite this edge case. Happy to provide more details if helpful.

<img width="844" height="779" alt="Image" src="https://github.com/user-attachments/assets/bcd69c6f-8a2c-4605-a36f-e0af65826a19" />

LGS-SwissIT · 7 months ago

Working Workaround: GUI Password Prompt Script

While waiting for native support, here's a working workaround using a shell script wrapper that provides GUI-based sudo authentication without exposing the password to Claude.

How It Works

  1. GUI Password Dialog: Uses zenity (GTK/GNOME) or kdialog (KDE) to display a native password prompt
  2. Secure Password Handling: Password is passed directly to sudo -S via stdin pipe - never written to disk, never exposed to the AI
  3. Command Display: Shows the command being executed in the dialog for user verification

The Script (~/.claude/scripts/sudo-prompt.sh)

#!/usr/bin/env bash
set -euo pipefail

if [[ $# -eq 0 ]]; then
    echo "Usage: $0 <command> [args...]" >&2
    exit 1
fi

get_dialog_tool() {
    if command -v zenity &>/dev/null; then
        echo "zenity"
    elif command -v kdialog &>/dev/null; then
        echo "kdialog"
    else
        echo ""
    fi
}

prompt_password() {
    local tool="$1"
    local cmd_display="$2"
    case "$tool" in
        zenity)
            zenity --password \
                --title="Authentication Required" \
                --text="Enter password to run:\n<b>$cmd_display</b>" 2>/dev/null
            ;;
        kdialog)
            kdialog --password "Enter password to run:\n$cmd_display" \
                --title "Authentication Required" 2>/dev/null
            ;;
    esac
}

dialog_tool=$(get_dialog_tool)
[[ -z "$dialog_tool" ]] && { echo "Error: zenity or kdialog required" >&2; exit 1; }

cmd_display="$*"
[[ ${#cmd_display} -gt 80 ]] && cmd_display="${cmd_display:0:77}..."

password=$(prompt_password "$dialog_tool" "$cmd_display") || exit 1
[[ -z "$password" ]] && { echo "Error: Empty password" >&2; exit 1; }

echo "$password" | sudo -S -p '' "$@"

Configuration

  1. Save the script and make executable:

``bash
mkdir -p ~/.claude/scripts
chmod +x ~/.claude/scripts/sudo-prompt.sh
``

  1. Add to ~/.claude/settings.json:

``json
{
"permissions": {
"allow": ["Bash(~/.claude/scripts/sudo-prompt.sh:*)"]
}
}
``

  1. Add to ~/.claude/CLAUDE.md:

``markdown
## Sudo Commands
When a command requires sudo, use:
~/.claude/scripts/sudo-prompt.sh <command> [args...]
``

Requirements

  • Linux with X11/Wayland
  • zenity (GNOME) or kdialog (KDE) installed
  • Install: sudo dnf install zenity or sudo apt install zenity

This keeps the password secure while enabling Claude to execute privileged commands with user consent via the GUI prompt.

ryota-murakami · 7 months ago

Adding an AI-Friendly Design Perspective

Great issue! I'd like to add some thoughts on how PTY support could be designed to maximize value for AI-assisted workflows, not just human UX.

The Context Gap in Agentic Loops

When an AI agent executes a command that requires interaction, the current flow creates a fundamental context gap:

AI Agent: Bash("npm init")
→ Command hangs (waiting for user input)
→ User switches to external terminal
→ User completes interactive prompts
→ User returns to AI session
→ AI has ZERO knowledge of what happened

The AI can't see what answers were provided, understand the resulting configuration, or continue the workflow with accurate context. This forces users to manually bridge context between terminals.

Workflows Where This Matters

| Workflow | Context Lost |
|----------|--------------|
| git rebase -i | Which commits were squashed/reordered |
| python -i REPL | Debugging sessions, experiments |
| npm init / pnpm create | Project configuration choices |
| vim/nvim edits | Manual code changes |
| psql / redis-cli | Database exploration results |

Proposed API Design (AI-Friendly)

For maximum AI utility, consider a session-based API that allows programmatic interaction:

// Session-based approach
const session = Bash.startSession({ 
  command: "python -i", 
  pty: true 
})

// Send input
await session.sendKeys("import pandas as pd\n")
await session.sendKeys("df.head()\n")

// Read output (AI can now see results)
const output = await session.readOutput()

// Control sequences
await session.sendKeys({ ctrl: true, key: "c" })

await session.end()

This would enable:

  1. Context-aware guidance - AI can see user's configuration choices and provide relevant follow-up suggestions
  2. REPL-based debugging - AI can observe debugging sessions and offer insights
  3. Complex git operations - AI can assist with interactive rebases while maintaining full visibility
  4. Learning from patterns - AI can observe how users configure tools to provide better recommendations

The tmux Proof of Concept

This pattern already works via tmux:

tmux new-session -d -s session 'python -i'
tmux send-keys -t session "print('hello')" Enter
tmux capture-pane -t session -p

Native integration would be more reliable, but this demonstrates the concept is viable.

Summary

PTY support would close the context gap that currently forces users to manually bridge information between interactive sessions and AI assistants. Designing with AI observability in mind would maximize the value of this feature.

+1 for this feature! 🚀

amol21p · 6 months ago

For anyone looking for a solution to this right now — I built an open-source MCP server that addresses this: mcp-interactive-terminal

It gives AI agents real, persistent terminal sessions via node-pty + xterm-headless. You can open REPLs, psql, SSH, rails console, etc. and send multiple commands to the same session. Clean text output (same terminal emulator as VS Code), smart command completion detection, and a two-step confirmation flow for dangerous commands.

One command to install:

claude mcp add terminal -- npx -y mcp-interactive-terminal

Works with Claude Code, Cursor, Windsurf, and any MCP client. Not a native Bash tool integration (that would still be great), but it covers the interactive use cases as an MCP server today.

nicovlr · 6 months ago

+1 — Concrete use case: I want to run interactive TUI games (built with blessed/curses/raw stdin) directly inside Claude Code while waiting for long-running tasks. Think Chrome's dino game but in the terminal, playable without leaving the Claude Code session.

The fact that Gemini CLI already ships this in v0.9.0 makes this even more compelling. Would love to see Claude Code catch up on this front.

moto-pu · 5 months ago

+1 for this. I'd like to add another concrete pain point caused by the same root issue: shell completions are entirely unavailable in the current one-shot, non-interactive bash mode.

Tools like git, docker, and npm all rely on completion scripts that are only loaded in interactive shells. Since the bash tool runs non-interactively, tab completions are silently absent — with no user-side workaround.

PTY support as proposed here would solve this too, since completions work naturally in interactive shell sessions.

Environment:

  • Claude Code: v2.1.69
  • Affected shell: bash
mattmenefee · 5 months ago

Use case: Lightweight interactive prompts with gum in slash commands

Adding a use case that's distinct from the full-TUI scenarios (vim, rebase) discussed above — lightweight interactive prompts mid-workflow using gum.

I maintain a set of shared slash commands (.claude/commands/*.md) for team workflows: /local-review, /ship-it, /start-work, /lint, /test. Several of these need quick user input during execution:

| Command | gum subcommand | Purpose |
|---------|-----------------|---------|
| /local-review | gum choose --no-limit | Multi-select which code review findings to fix |
| /ship-it | gum confirm | Confirm before force-pushing or creating a Linear issue |
| /start-work | gum filter | Fuzzy-search assigned Linear issues to pick one |
| /lint, /test | gum spin | Show a spinner while linters/tests run |

Today, all of these fail with open /dev/tty: device not configured because the Bash tool runs without a controlling terminal. The fallback is text-based ("type F1, F3 or 'skip'"), which works but loses the interactive UX that makes gum valuable.

Why gum is a compelling case for PTY support:

Unlike vim or a REPL, gum interactions are brief and self-terminating — the user makes a selection and the command exits. There's no long-running session to manage. This makes it a lower-complexity entry point for PTY support:

  • gum choose / gum filter — present options, user selects, exits
  • gum confirm — yes/no, exits
  • gum input / gum write — collect text, exits
  • gum spin — show progress, exits when the wrapped command finishes
  • gum table — display formatted data, exits

These are exactly the kind of "focused interactive moments" that would benefit from a focus keybinding approach (like the Ctrl+F mentioned in the issue description) — the interaction is brief, the context return is immediate, and Claude doesn't need to interpret the terminal output.

The broader pattern here: there's a growing ecosystem of terminal UI tools (gum, fzf, skim, bat, glow) that enhance developer workflows but are completely inaccessible from Claude Code today. PTY support would unlock all of them.

nielsbosma · 5 months ago

I built an open-source tool that solves this today as an external companion to Claude Code: https://github.com/nielsbosma/shellwright — a PTY session broker that any agent can shell out to (or a playwright for CLIs)

shellwright start --name build -- npm run build
shellwright wait build --for "Continue?" --timeout 30
shellwright send build "y"
shellwright read build --tail 10

It handles all the cases mentioned here — interactive prompts, password inputs, REPLs, TUI apps — with prompt detection, clean ANSI-stripped output, and cursor-based reads. Cross-platform (Windows ConPTY + Unix), daemon-backed so sessions persist across invocations.

Install: cargo install shellwright or npx shellwright

Would love feedback from anyone hitting this limitation.

mikegilchrist · 4 months ago

Workaround for sudo-over-SSH without a PTY: global sudo timestamp + PreToolUse hook

Until PTY support lands, here's a workaround that lets Claude run sudo in SSH+tmux workflows where SUDO_ASKPASS GUI helpers can't work (no X display).

Setup/etc/sudoers.d/claude-global-timestamp:

Defaults timestamp_type=global
Defaults timestamp_timeout=20

timestamp_type=global makes sudo's credential cache per-user instead of per-tty. With that, authenticating in any terminal (Ctrl-Z suspend + parent shell, sibling tmux pane, or a second SSH session) caches credentials that Claude's Bash-tool subprocess can then reuse.

Flow:

  1. Claude hits a sudo tool call
  2. A PreToolUse hook probes sudo -n true
  3. Cold → hook blocks with ask decision and tells the user: "Ctrl-Z, sudo -v, fg, retry"
  4. User authenticates in the real tty
  5. Cache propagates; hook allows; tool call succeeds
  6. Good for 20 minutes; sudo -k anywhere ends the session

Gotcha: Claude's ! command prefix does not give you a real tty — it runs through the same ttyless Bash-tool path, so plain sudo still fails there. Must use Ctrl-Z + fg, or a separate pane.

Hook sketch (registered under PreToolUse.Bash):

#!/bin/bash
set -o pipefail
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command // empty')
[ -z "$command" ] && exit 0

echo "$command" | grep -Eq '(^|[^a-zA-Z0-9_/.-])sudo([[:space:]]|$)' || exit 0

# Cache-management or explicit -n → allow
if echo "$command" | grep -Eq '(^|[^a-zA-Z0-9_/.-])sudo[[:space:]]+-[vkKn]([[:space:]]|$)'; then
    jq -n '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","permissionDecisionReason":"sudo cache-management or non-interactive"}}'
    exit 0
fi

if sudo -n true 2>/dev/null; then
    jq -n '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","permissionDecisionReason":"sudo cache warm"}}'
    exit 0
fi

msg='Sudo cache is cold. Press Ctrl-Z, run `sudo -v`, then `fg` and tell me to retry.'
jq -n --arg msg "$msg" '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":$msg}}'

Trade-offs vs alternatives:

| Approach | SSH-friendly | Auto-expires | User control |
|---|---|---|---|
| Full NOPASSWD | yes | no | weak |
| Narrow NOPASSWD | yes | no | rigid |
| SUDO_ASKPASS (GUI) | no | yes | per-cmd |
| Global timestamp + hook | yes | 20 min | strong |

Trust model: during the 20-minute window, any process running as the user can sudo without prompting — same practical boundary as the interactive shell you're typing in. Fine for single-user workstations with active supervision; not fine for multi-user or unattended agents.

Posting this because #1135 (where this would be most on-topic) is locked. Native PTY support (this issue) would make all of this unnecessary, but in the meantime this is a cleaner fallback than copy-pasting every sudo command.

xdrr · 4 months ago

I built an MCP that gives a complete, private, VT100-style pseudoterminal (and keyboard) to claude-code: https://github.com/xdrr/ptyai

You can try it out with: npm install -g ptyai

It works surprisingly well; in fact, I was shocked at how much better claude-code works when there's a full pseudoterminal available. It's almost as if Anthropic's models have the training and were just waiting for the tooling to support a terminal.

Rather than the session broker design @nielsbosma describes, the ptyai MCP lets the agent see and type everything that appears on the terminal verbatim. It has complete VT100 support and configurable gemonetry.

I added an install script so that users can completely replace the Bash() tool with the ptyai MCP.

claude-code doesn't need special prompting, it just instinctively knows how to use a terminal and goes about its business running all those interactive programs it could never run until now... Marvelous!