Bug: SendPrompt sends text before CLI (Codex/others) is ready, prompt lost

Author: DragonFSKYCreated Mar 17, 2026Updated May 30, 2026

Bug Description

When creating a new instance via Shift+N (KeyPrompt) with a prompt, the prompt text is sent to the CLI before the CLI finishes initialization, causing the prompt to be lost. The CLI ends up in a waiting-for-input state with no prompt received.

Steps to Reproduce

  1. Start cs (claude-squad)
  2. Press Shift+N to create a new instance with prompt
  3. Enter a title, then enter a prompt text
  4. Select program (e.g., Codex CLI)
  5. Submit

Expected: The CLI receives the prompt and starts working on it. Actual: The CLI starts but stays at its input prompt, the prompt text is never received.

Root Cause Analysis

In app.go:291-295, SendPrompt() is called immediately after instance.Start() returns via instanceStartedMsg, with no wait for CLI readiness:

go
// app.go:291-295
if msg.instance.Prompt != "" {
    if err := msg.instance.SendPrompt(msg.instance.Prompt); err != nil {
        log.ErrorLog.Printf("failed to send prompt: %v", err)
    }
    msg.instance.Prompt = ""
}

The SendPromptSendKeys chain writes raw bytes directly to the PTY (tmux.go:228-230):

go
func (t *TmuxSession) SendKeys(keys string) error {
    _, err := t.ptmx.Write([]byte(keys))
    return err
}

The race condition:

  1. instance.Start() creates tmux session and launches the CLI program
  2. Start() returns → instanceStartedMsg is sent
  3. SendPrompt() writes prompt text to PTY immediately
  4. But the CLI (Codex, Claude, etc.) is still initializing (loading MCP servers, setting up TUI, etc.)
  5. When the CLI enters raw terminal mode, the previously buffered PTY bytes are lost/discarded
  6. CLI shows its input prompt but the text is gone

Additional Issue: No Codex readiness detection

In tmux.go:242-249, HasUpdated() only detects readiness for Claude, Aider, and Gemini. Codex has no detection at all:

go
if t.program == ProgramClaude {
    hasPrompt = strings.Contains(content, "No, and tell Claude what to do differently")
} else if strings.HasPrefix(t.program, ProgramAider) {
    hasPrompt = strings.Contains(content, "(Y)es/(N)o/(D)on't ask again")
} else if strings.HasPrefix(t.program, ProgramGemini) {
    hasPrompt = strings.Contains(content, "Yes, allow once")
}
// No Codex handling

Similarly, CheckAndHandleTrustPrompt() (tmux.go:163-178) has no Codex-specific handling.

Suggested Fix

Add a readiness polling mechanism before sending the prompt:

go
func (i *Instance) WaitForReadyAndSendPrompt(prompt string, timeout time.Duration) error {
    deadline := time.Now().Add(timeout)
    for time.Now().Before(deadline) {
        content, err := i.tmuxSession.CapturePaneContent()
        if err == nil && isCliReady(content, i.Program) {
            return i.SendPrompt(prompt)
        }
        time.Sleep(500 * time.Millisecond)
    }
    return i.SendPrompt(prompt)
}

And add Codex input prompt detection (e.g., checking for > prompt pattern).

Environment

  • OS: Linux (Debian)
  • claude-squad version: latest from main
  • CLI: OpenAI Codex v0.115.0 (also potentially affects other slow-starting CLIs)
  • Program config: codex