Skip to content

Feature Request: Add Background Bash Execution (Like Claude Code’s Ctrl+b) #1970

Closed as not planned

Description

@elloydfetch

Feature Request: Support Long-Running Bash Processes in the Background (Async Execution)

Summary

Claude Code includes a feature that allows developers to run bash commands in the background without blocking the agent/chat interface. This enables long-running tasks (e.g., builds, servers, data processing) to continue running while the developer interacts with the AI for other tasks.

I propose adding similar functionality to OpenCode.

Motivation

Currently, when a bash command is run in OpenCode via the bash tool, the session is blocked until the command completes. This makes workflows involving:

  • Development servers
  • Continuous monitoring scripts
  • Large builds or data processing jobs

…slow and cumbersome, since you can’t continue using the AI assistant while those commands are still running.

Background execution would:

  • Keep the AI assistant responsive
  • Allow parallel workstreams
  • Reduce the need to switch out of OpenCode to manage long-running jobs in a separate shell

Reference: Claude Code’s Implementation

Claude Code provides Background Commands with a simple shortcut (Ctrl+b) to run bash processes asynchronously.
Reference: Claude Code FAQ – Background Commands

“Background commands allow you to run bash processes in the background using Ctrl+b, enabling Claude to continue working on other tasks while long-running processes execute. Great for development servers and monitoring.”

Proposed Design Ideas

  • Syntax Option: Add a background: true parameter to the bash tool.
  • Shortcut Key: Similar to Claude Code, provide a hotkey to run the currently typed bash command as a background job.
  • Job Management: Include a simple job list/kill command to view or stop background processes.
  • Output Handling: Stream or store background process logs for later inspection.

Workarounds Today

  • Using & or nohup inside the bash tool sometimes works, but behavior is inconsistent and often tied to the session lifecycle.
  • Running tasks outside of OpenCode in a multiplexer (tmux, screen)—but this defeats the purpose of integrated AI-assisted workflows.

Impact

Implementing this feature would make OpenCode far more usable for developers with mixed workloads and align it with modern AI coding assistants’ capabilities.


Related links:

Activity

  1. github-actions commented on Aug 15, 2025

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    While these issues address different aspects of bash tool problems (hanging, getting stuck, output handling), none specifically address background/async execution like your feature request. Your proposal for background bash execution appears to be a new and distinct feature request.

  2. the-vampiire commented on Aug 15, 2025

    @the-vampiire

    idk if it helps but this is what i had created a while ago to handle running dev servers without the agent trying to start multiple, getting stuck by not realizing its an infinite process, and being able to look at logs as needed while we work.

    this is just for "dev" mode but conceptually it could be used for other processes besides a dev server.

    #!/bin/bash
    
    # Generic development process manager
    # Usage: ./dev.sh start "your dev command here"
    PID_FILE=".dev.pid"
    LOG_FILE=".dev.log"
    
    run() {
        case "$1" in
            start)   shift; start "$@" ;;
            stop)    stop ;;
            restart) shift; restart "$@" ;;
            logs)    shift; logs "$@" ;;
            status)  status ;;
            *)       echo "Usage: $0 {start|stop|restart|logs|status} [command]" ;;
        esac
    }
    
    start() {
        if [ $# -eq 0 ]; then
            echo "Usage: $0 start \"command to run\""
            return 1
        fi
        
        if [ -f "$PID_FILE" ]; then
            if kill -0 $(cat "$PID_FILE") 2>/dev/null; then
                echo "Process already running (PID: $(cat $PID_FILE))"
                return 1
            else
                echo "Removing stale PID file"
                rm "$PID_FILE"
            fi
        fi
        
        echo "Starting: $*"
        echo "=== Started at $(date) ===" > "$LOG_FILE"
        "$@" >> "$LOG_FILE" 2>&1 &
        PID=$!
        echo $PID > "$PID_FILE"
        echo "Process started (PID: $PID)"
        
        # Give it a moment to start
        sleep 2
        
        # Verify it's still running
        if kill -0 "$PID" 2>/dev/null; then
            echo "Process is running. Logs: ./dev.sh logs"
        else
            echo "Process failed to start. Check logs: ./dev.sh logs"
            rm "$PID_FILE"
        fi
    }
    
    stop() {
        if [ -f "$PID_FILE" ]; then
            PID=$(cat "$PID_FILE")
            if kill -0 "$PID" 2>/dev/null; then
                echo "Stopping process (PID: $PID)"
                kill "$PID"
                rm "$PID_FILE"
            else
                echo "Process not running"
                rm "$PID_FILE"
            fi
        else
            echo "No PID file found"
        fi
    }
    
    restart() {
        if [ $# -eq 0 ]; then
            echo "Usage: $0 restart \"command to run\""
            return 1
        fi
        stop
        sleep 1
        start "$@"
    }
    
    logs() {
        if [ ! -f "$LOG_FILE" ]; then
            echo "No log file found"
            return 1
        fi
        
        case "${1:-tail}" in
            tail|follow)
                echo "Following logs (Ctrl+C to exit)..."
                tail -f "$LOG_FILE"
                ;;
            recent)
                lines="${2:-50}"
                echo "Last $lines lines:"
                tail -"$lines" "$LOG_FILE"
                ;;
            clear)
                > "$LOG_FILE"
                echo "Log file cleared"
                ;;
            all)
                cat "$LOG_FILE"
                ;;
            search)
                if [ -z "$2" ]; then
                    echo "Usage: $0 logs search <pattern>"
                    echo "Example: $0 logs search ERROR"
                    return 1
                fi
                echo "Searching for: $2"
                grep -n --color=auto "$2" "$LOG_FILE"
                ;;
            *)
                echo "Usage: $0 logs [tail|recent|clear|all|search] [args]"
                echo "  tail/follow     - Follow logs in real-time (default)"
                echo "  recent [N]      - Show last N lines (default: 50)"
                echo "  clear           - Clear log file"  
                echo "  all             - Show entire log"
                echo "  search <pattern> - Search logs with grep"
                echo ""
                echo "Examples:"
                echo "  $0 logs recent 100    # Last 100 lines"
                echo "  $0 logs search ERROR  # Find error messages"
                echo "  $0 logs search 'POST.*ws' # Find WebSocket POST requests"
                ;;
        esac
    }
    
    status() {
        if [ -f "$PID_FILE" ]; then
            PID=$(cat "$PID_FILE")
            if kill -0 "$PID" 2>/dev/null; then
                echo "Process running (PID: $PID)"
                echo "Log file: $LOG_FILE"
            else
                echo "Process not running (stale PID file)"
            fi
        else
            echo "Process not running"
        fi
    }
    
    run
  3. ironbelly commented on Oct 9, 2025

    @ironbelly

    Agreed that tmux is a workaround but would be nice to have this built in to be on part with claude code

  4. rekram1-node commented on Oct 9, 2025

    @rekram1-node
    Collaborator

    yeah makes sense

  5. waylaidwanderer commented on Nov 28, 2025

    @waylaidwanderer

    I built an MCP server for this, which is a solid alternative for now:

    https://github.com/waylaidwanderer/background-process-mcp

  6. remorses commented on Dec 6, 2025

    @remorses
    Contributor

    tmux works really well for running commands in background

    it can

    • start bash commands (sessions) in background with a name
    • list sessions
    • kill sessions
    • get last N logs for a session

    that's all you need, with the added advantage that you can attach to these sessions yourself to send interactive commands yourself! something that Claude Code does not have

    you can give this document to your agent to let it know the commands to use https://github.com/remorses/AGENTS.md/blob/main/tmux.md

  7. junmediatek commented on Dec 18, 2025

    @junmediatek
    Contributor

    yeah makes sense

    hi @thdxr & @rekram1-node
    Could you share the estimated completion time for this feature?

  8. rekram1-node commented on Dec 18, 2025

    @rekram1-node
    Collaborator

    no official timeline I guess we would prolly roll out experimental version first and see how it does

  9. junmediatek commented on Dec 18, 2025

    @junmediatek
    Contributor

    no official timeline I guess we would prolly roll out experimental version first and see how it does

    @rekram1-node So when will the experimental version be released?

  10. shekohex commented on Dec 19, 2025

    @shekohex
    Contributor

    you can also use opencode-pty plugin, it does exactly that and more: https://github.com/shekohex/opencode-pty

  11. PlaneInABottle commented on Feb 12, 2026

    @PlaneInABottle

    opencde-pty has a major limitation, you have to configure the permissions beforehand, it can't trigger ask permission tool of the opencode.

  12. shekohex commented on Feb 12, 2026

    @shekohex
    Contributor

    opencde-pty has a major limitation, you have to configure the permissions beforehand, it can't trigger ask permission tool of the opencode.

    That's a limitation of opencode plugins API rather than the plugin itself.

  13. PlaneInABottle commented on Feb 12, 2026

    @PlaneInABottle

    opencde-pty has a major limitation, you have to configure the permissions beforehand, it can't trigger ask permission tool of the opencode.

    That's a limitation of opencode plugins API rather than the plugin itself.

    Oh yes, sorry for that, I have described wrongly.

    We should either have support in the plugins API or native background bash execution.

  14. Shane0xM commented on Mar 16, 2026

    @Shane0xM

    I’m planning a narrow PR for this: add an opt-in TUI keybind that submits normal prompts via the existing prompt_async path. No changes to sidebar/running-state UI, subagent visibility, or background-task orchestration. I’ll keep default behavior unchanged and make the keybind opt-in rather than remapping ctrl+b by default.

  15. workdocyeye commented on Apr 25, 2026

    @workdocyeye

    Ran into this myself and threw together a skill that handles it:
    https://github.com/workdocyeye/server-manager

    Basically runs the dev command through cmd /c with output redirected to a log file — no blocking, can read the output anytime. Works on Windows with PowerShell, covers the usual dev servers (npm, streamlit, go run, etc).

    Not a native solution, but does the job while you wait for the official feature.

  16. abrar360 commented on Apr 29, 2026

    @abrar360

    Posting a concrete implementation proposal for this issue. I have a working branch implementing background bash execution with a completion-notification mechanism; sharing the design here for maintainer review before opening a PR.

    This proposal covers the programmatic side — the equivalent of CC's run_in_background: true flag on the bash tool. It does not cover a TUI keybind for mid-flight backgrounding of a running command; that's listed under "not in scope" below and would be a natural follow-up.

    Proposed approach

    • Add an optional run_in_background: boolean parameter to the bash tool. When true, the spawn returns immediately with a task ID + output file path; the command runs detached, with output streamed to a file in opencode's existing truncation directory (TRUNCATION_DIR).
    • Add a bash_kill (or generic task_stop) tool that takes a task ID and terminates the running task. Reading partial/final output uses the existing Read tool against the output file path — no new bash_output tool needed.
    • On natural completion or stall-detection, attach a synthetic part to the next user message handed to the LLM (status, exit code, output file path). This reuses opencode's existing insertReminders / synthetic-text-part convention rather than introducing a new attachment vocabulary.
    • A one-shot stall watchdog detects commands blocked on stdin (heuristic on the output tail) and emits a notification with a kill-and-rerun-with-piped-input recovery instruction.

    Internally: a BackgroundTask service forks workers into the instance scope (so they survive the bash tool returning), publishes Bus events on lifecycle transitions, and a notifier subscribes to those events and queues per-session notifications. Drain happens at the same loop seam as insertReminders in SessionPrompt.runLoop.

    The design deliberately replaces CC's notified flag (which is set in two places — enqueueShellNotification and stopTask) with a structural single-chokepoint: kill() does not publish the Exited event, and the worker's catchCause swallows interrupt-causes. Result: the only path to a notification is a natural worker exit. No flag coordination across two call sites.

    Open questions for maintainers

    I'd value direction on these before opening a PR:

    1. Naming / scope. Strictly a bash feature (BashTasks service, bash_kill tool), or generic background-task framework now (BackgroundTask service, task_stop tool with bash being the first kind)? Affects future extensibility for things like [feat] True Async/Background Sub-Agent Delegation #5887.
    2. Notification envelope. CC wraps the completion notification in a <task-notification> XML structure; my current draft uses opencode's <system-reminder> envelope for consistency with insertReminders. Match CC's format for closer behavioral alignment, or keep the opencode convention?
    3. Feature flag. Ship behind an experimental flag (e.g. OPENCODE_EXPERIMENTAL_BASH_BACKGROUND) until it has real-world soak time?
    4. Kill-after-completion semantics. CC throws "not running" if you try to kill a finished task; I made it a no-op returning the existing status. Either is defensible — preference?

    Not in scope (deferred from CC parity)

    Calling these out so it's clear what's not being proposed for v1:

    • Auto-backgrounding of long-blocking commands (CC moves them to background after ~15s in assistant mode)
    • Mid-flight backgrounding via TUI keybind (the Ctrl+B referenced in this issue's title)
    • Per-agent notification routing (would scope by sessionID for now)
    • TUI-side fold of consecutive completion notifications
    • Notification priority levels

    Each could be a follow-up if there's demand.

    Status

    Working branch committed locally; ready to open a PR once design questions have direction. Originally filed as #24885 before realizing this thread existed — closing that one and consolidating here.

  17. pan17 commented on Jun 10, 2026

    @pan17

    I've been using the opencode-pty plugin to replace this feature. As expected, it also stopped working in recent opencode versions. Looking forward to having similar functionality built-in.

  18. ImBIOS commented on Jun 15, 2026

    @ImBIOS
    Contributor
    Image

    Isn't it already shipped? If yes, this issue should be closed or automatically closed if 90 days without further activities.

  19. Ploppy3 commented on Jun 23, 2026

    @Ploppy3
    Image Isn't it already shipped? If yes, this issue should be closed or automatically closed if 90 days without further activities.

    No this is for agents only, and I think it's currently behind an experimental flag.

  20. github-actions commented on Aug 23, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

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

Metadata

Metadata

Assignees

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