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:
kustrun
2026-01-02 20:27:26 +01:00
parent 3c4190597f
commit 94857fc913
5 changed files with 78 additions and 2 deletions

View File

@@ -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

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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

View File

@@ -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) {