Bug: SendPrompt sends text before CLI (Codex/others) is ready, prompt lost
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
- Start
cs(claude-squad) - Press Shift+N to create a new instance with prompt
- Enter a title, then enter a prompt text
- Select program (e.g., Codex CLI)
- 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 SendPrompt → SendKeys 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:
instance.Start()creates tmux session and launches the CLI programStart()returns →instanceStartedMsgis sentSendPrompt()writes prompt text to PTY immediately- But the CLI (Codex, Claude, etc.) is still initializing (loading MCP servers, setting up TUI, etc.)
- When the CLI enters raw terminal mode, the previously buffered PTY bytes are lost/discarded
- 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 handlingSimilarly, 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
Source: smtg-ai/claude-squad