fix the sendkeys function and hit enter on "Stop and wait for limit to reset"-dialog

This commit is contained in:
Simon
2026-05-26 12:19:57 +00:00
committed by ForgeCode
parent 389a251ed1
commit 2804e5f52f
3 changed files with 38 additions and 4 deletions
+9 -1
View File
@@ -1,4 +1,4 @@
import { stripAnsi, isRateLimited, findRateLimitMessage } from './patterns.js';
import { stripAnsi, isRateLimited, findRateLimitMessage, isLimitPrompt } from './patterns.js';
import { parseResetTime, calculateWaitMs } from './time-parser.js';
import { capturePane, sendKeys, getPaneCommand, isProcessForeground } from './tmux.js';
import { loadConfig } from './config.js';
@@ -67,6 +67,13 @@ export async function processOneTick(state, tmuxAdapter, pane, config, isAlive)
return 'waiting';
}
// Detect the interactive "What do you want to do?" prompt and auto-select "Stop and wait"
if (isLimitPrompt(stripped)) {
state.waitUntil = Date.now() + 30_000;
await tmuxAdapter.sendKeys(pane, ''); // Empty string = just press Enter
return 'prompt-confirmed';
}
return 'monitoring';
}
@@ -95,6 +102,7 @@ export async function startMonitor(pane, pid) {
}
if (result === 'retried') await logger.info(`Sent retry message (attempt ${state.attempts})`);
if (result === 'user-continued') await logger.info('User already continued. Attempt counter reset.');
if (result === 'prompt-confirmed') await logger.info('Limit prompt detected. Sent Enter to select "Stop and wait for limit to reset".');
if (result === 'max-retries') await logger.warn(`Max retries (${config.maxRetries}) reached. Monitor still active but will not send further retries until rate limit clears.`);
if (result === 'skipped-not-claude') await logger.warn(`Foreground is "${state._lastForeground}", not Claude. Skipping send-keys. (Add to foregroundCommands in ~/.claude-auto-retry.json if this is wrong)`);
} catch (err) {
+12 -1
View File
@@ -23,7 +23,7 @@ export function stripAnsi(text) {
// Detection: find a "limit" line and a "resets" line within 6 lines of each other.
const LIMIT_PATTERNS = [
/(?:hit|exceeded|reached).*?limit/i // matches: "hit ... limit" with anything in between
/(?:hit|exceeded|reached).*?limit/i, // "hit/exceeded/reached ... limit" (handles "You've hit your session limit")
/\d+-hour limit/i, // "5-hour limit"
/limit reached/i, // "limit reached"
/usage limit/i, // "usage limit"
@@ -85,3 +85,14 @@ export function findRateLimitMessage(text, customPatterns = []) {
return null;
}
// Detects the interactive Claude Code prompt asking user to wait or upgrade
// "What do you want to do?"
// " 1. Stop and wait for limit to reset"
// " 2. Upgrade your plan"
// We auto-select option 1 by sending Enter
export function isLimitPrompt(text) {
const stripped = stripAnsi(text);
return /What do you want to do\?/i.test(stripped) &&
/Stop and wait.*limit/i.test(stripped);
}
+17 -2
View File
@@ -8,7 +8,13 @@ export function buildCaptureArgs(pane, lines = 200) {
}
export function buildSendKeysArgs(pane, text) {
return ['send-keys', '-t', pane, text, 'Enter'];
// Use -l flag to send text literally, then C-m to send (carriage return/Enter)
// This ensures special characters and spaces in the text are handled correctly
if (text) {
return [['send-keys', '-t', pane, '-l', text], ['send-keys', '-t', pane, 'C-m']];
}
// For empty string, just send C-m
return [['send-keys', '-t', pane, 'C-m']];
}
export function buildDisplayArgs(pane, format) {
@@ -32,7 +38,16 @@ export async function capturePane(pane, lines = 200) {
}
export async function sendKeys(pane, text) {
await execFileAsync('tmux', buildSendKeysArgs(pane, text));
const args = buildSendKeysArgs(pane, text);
// If buildSendKeysArgs returns nested arrays (multiple commands), execute them sequentially
if (Array.isArray(args[0])) {
for (const cmdArgs of args) {
await execFileAsync('tmux', cmdArgs);
}
} else {
// Single command
await execFileAsync('tmux', args);
}
}
export async function getPaneCommand(pane) {