Skip to content

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

@DragonFSKY

Description

@DragonFSKY

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:

// 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):

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:

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:

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

Metadata

Metadata

Assignees

No one assigned

    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