fix(session): Auto-accept Claude bypass permissions warning dialog
When Claude starts with --dangerously-skip-permissions, it shows a warning dialog requiring Down+Enter to accept. This blocked automated polecat and agent startup. Added AcceptBypassPermissionsWarning() to tmux package that: - Checks if the warning dialog is present by capturing pane content - Only sends Down+Enter if "Bypass Permissions mode" text is found - Avoids interfering with sessions that don't show the warning Updated all Claude startup locations: - session/manager.go (polecat sessions) - cmd/up.go (mayor, witness, crew, polecat cold starts) - daemon/daemon.go (crashed polecat restarts) - daemon/lifecycle.go (role session starts)
This commit is contained in:
@@ -281,6 +281,10 @@ func ensureSession(t *tmux.Tmux, sessionName, workDir, role string) error {
|
||||
if err := t.WaitForCommand(sessionName, constants.SupportedShells, constants.ClaudeStartTimeout); err != nil {
|
||||
// Non-fatal
|
||||
}
|
||||
|
||||
// Accept bypass permissions warning dialog if it appears.
|
||||
_ = t.AcceptBypassPermissionsWarning(sessionName)
|
||||
|
||||
time.Sleep(constants.ShutdownNotifyDelay)
|
||||
|
||||
// Inject startup nudge for predecessor discovery via /resume
|
||||
@@ -330,6 +334,10 @@ func ensureWitness(t *tmux.Tmux, sessionName, rigPath, rigName string) error {
|
||||
if err := t.WaitForCommand(sessionName, constants.SupportedShells, constants.ClaudeStartTimeout); err != nil {
|
||||
// Non-fatal
|
||||
}
|
||||
|
||||
// Accept bypass permissions warning dialog if it appears.
|
||||
_ = t.AcceptBypassPermissionsWarning(sessionName)
|
||||
|
||||
time.Sleep(constants.ShutdownNotifyDelay)
|
||||
|
||||
// Inject startup nudge for predecessor discovery via /resume
|
||||
@@ -557,6 +565,10 @@ func ensureCrewSession(t *tmux.Tmux, sessionName, crewPath, rigName, crewName st
|
||||
if err := t.WaitForCommand(sessionName, constants.SupportedShells, constants.ClaudeStartTimeout); err != nil {
|
||||
// Non-fatal
|
||||
}
|
||||
|
||||
// Accept bypass permissions warning dialog if it appears.
|
||||
_ = t.AcceptBypassPermissionsWarning(sessionName)
|
||||
|
||||
time.Sleep(constants.ShutdownNotifyDelay)
|
||||
|
||||
// Inject startup nudge for predecessor discovery via /resume
|
||||
@@ -661,6 +673,10 @@ func ensurePolecatSession(t *tmux.Tmux, sessionName, polecatPath, rigName, polec
|
||||
if err := t.WaitForCommand(sessionName, constants.SupportedShells, constants.ClaudeStartTimeout); err != nil {
|
||||
// Non-fatal
|
||||
}
|
||||
|
||||
// Accept bypass permissions warning dialog if it appears.
|
||||
_ = t.AcceptBypassPermissionsWarning(sessionName)
|
||||
|
||||
time.Sleep(constants.ShutdownNotifyDelay)
|
||||
|
||||
// Inject startup nudge for predecessor discovery via /resume
|
||||
|
||||
@@ -695,6 +695,13 @@ func (d *Daemon) restartPolecatSession(rigName, polecatName, sessionName string)
|
||||
return fmt.Errorf("sending startup command: %w", err)
|
||||
}
|
||||
|
||||
// Wait for Claude to start, then accept bypass permissions warning if it appears.
|
||||
// This ensures automated restarts aren't blocked by the warning dialog.
|
||||
if err := d.tmux.WaitForCommand(sessionName, constants.SupportedShells, constants.ClaudeStartTimeout); err != nil {
|
||||
// Non-fatal - Claude might still start
|
||||
}
|
||||
_ = d.tmux.AcceptBypassPermissionsWarning(sessionName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -364,6 +364,13 @@ func (d *Daemon) restartSession(sessionName, identity string) error {
|
||||
return fmt.Errorf("sending startup command: %w", err)
|
||||
}
|
||||
|
||||
// Wait for Claude to start, then accept bypass permissions warning if it appears.
|
||||
// This ensures automated role starts aren't blocked by the warning dialog.
|
||||
if err := d.tmux.WaitForCommand(sessionName, constants.SupportedShells, constants.ClaudeStartTimeout); err != nil {
|
||||
// Non-fatal - Claude might still start
|
||||
}
|
||||
_ = d.tmux.AcceptBypassPermissionsWarning(sessionName)
|
||||
|
||||
// Note: gt prime is handled by Claude's SessionStart hook, not injected here.
|
||||
// Injecting it via SendKeysDelayed causes rogue text to appear in the terminal.
|
||||
|
||||
|
||||
@@ -193,12 +193,18 @@ func (m *Manager) Start(polecat string, opts StartOptions) error {
|
||||
// Non-fatal warning - Claude might still start
|
||||
}
|
||||
|
||||
// Accept bypass permissions warning dialog if it appears.
|
||||
// When Claude starts with --dangerously-skip-permissions, it shows a warning that
|
||||
// requires pressing Down to select "Yes, I accept" and Enter to confirm.
|
||||
// This is needed for automated polecat startup.
|
||||
_ = m.tmux.AcceptBypassPermissionsWarning(sessionID)
|
||||
|
||||
// Wait for Claude to be fully ready at the prompt (not just started)
|
||||
// PRAGMATIC APPROACH: Use fixed delay rather than detection.
|
||||
// WaitForClaudeReady has false positives (detects > in various contexts).
|
||||
// Claude startup takes ~5-8 seconds on typical machines.
|
||||
// 10 second delay is conservative but reliable.
|
||||
time.Sleep(10 * time.Second)
|
||||
// Reduced from 10s to 8s since AcceptBypassPermissionsWarning already adds ~1.2s.
|
||||
time.Sleep(8 * time.Second)
|
||||
|
||||
// Inject startup nudge for predecessor discovery via /resume
|
||||
// This becomes the session title in Claude Code's session picker
|
||||
|
||||
@@ -265,6 +265,46 @@ func (t *Tmux) NudgePane(pane, message string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AcceptBypassPermissionsWarning dismisses the Claude Code bypass permissions warning dialog.
|
||||
// When Claude starts with --dangerously-skip-permissions, it shows a warning dialog that
|
||||
// requires pressing Down arrow to select "Yes, I accept" and then Enter to confirm.
|
||||
// This function checks if the warning is present before sending keys to avoid interfering
|
||||
// with sessions that don't show the warning (e.g., already accepted or different config).
|
||||
//
|
||||
// Call this after starting Claude and waiting for it to initialize (WaitForCommand),
|
||||
// but before sending any prompts.
|
||||
func (t *Tmux) AcceptBypassPermissionsWarning(session string) error {
|
||||
// Wait for the dialog to potentially render
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// Check if the bypass permissions warning is present
|
||||
content, err := t.CapturePane(session, 30)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Look for the characteristic warning text
|
||||
if !strings.Contains(content, "Bypass Permissions mode") {
|
||||
// Warning not present, nothing to do
|
||||
return nil
|
||||
}
|
||||
|
||||
// Press Down to select "Yes, I accept" (option 2)
|
||||
if _, err := t.run("send-keys", "-t", session, "Down"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Small delay to let selection update
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
// Press Enter to confirm
|
||||
if _, err := t.run("send-keys", "-t", session, "Enter"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPaneCommand returns the current command running in a pane.
|
||||
// Returns "bash", "zsh", "claude", "node", etc.
|
||||
func (t *Tmux) GetPaneCommand(session string) (string, error) {
|
||||
|
||||
Reference in New Issue
Block a user