Skip to content
24 changes: 24 additions & 0 deletions .changeset/claude-resume-command-in-logs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
'@link-assistant/hive-mind': patch
---

Fix: Move Claude CLI resume command from GitHub comment to logs

When usage limit is reached, the GitHub comment now only mentions the
`--auto-continue-on-limit-reset` option instead of showing bash commands.
This is more user-friendly for Telegram bot users who don't use CLI commands directly.

The Claude CLI resume command is still available in the logs (in the collapsed
block or gist link), allowing advanced users to resume manually if needed:

```bash
(cd "/tmp/gh-issue-solver-..." && claude --resume session-id)
```

Changes:

- GitHub comments now only suggest using the `--auto-continue-on-limit-reset` option
- Resume commands are kept in logs only (not in the visible comment)
- Session ID is still shown for reference

Fixes #942
38 changes: 38 additions & 0 deletions src/claude.command-builder.lib.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,43 @@ export const buildClaudeResumeCommand = ({ tempDir, sessionId, claudePath = 'cla
return `(cd "${tempDir}" && ${claudePath} ${args})`;
};

/**
* Build the Claude CLI autonomous resume command
*
* This generates a fully autonomous command that includes all flags needed to run
* without user interaction. The user can copy-paste this command and it will
* continue the session autonomously.
*
* The command includes:
* - --resume <sessionId>: Resume from the specified session
* - --output-format stream-json: For streaming output
* - --dangerously-skip-permissions: Skip interactive permission prompts
* - --model <model>: Use the same model as the original session
* - -p "Continue.": Simple prompt to continue the work
*
* Note: This function is specifically designed for Claude CLI (--tool claude)
* and should only be used when the tool is 'claude' or undefined (defaults to claude).
*
* @param {Object} options - Options for building the command
* @param {string} options.tempDir - The working directory (e.g., /tmp/gh-issue-solver-...)
* @param {string} options.sessionId - The session ID to resume
* @param {string} options.claudePath - Path to the claude CLI binary (defaults to 'claude')
* @param {string} [options.model] - The model to use (e.g., 'sonnet', 'opus', 'claude-sonnet-4-20250514')
* @returns {string} - The full autonomous resume command
*/
export const buildClaudeAutonomousResumeCommand = ({ tempDir, sessionId, claudePath = 'claude', model }) => {
let args = `--resume ${sessionId} --output-format stream-json --dangerously-skip-permissions`;

if (model) {
args += ` --model ${model}`;
}

// Add a simple "Continue." prompt to continue the autonomous work
args += ` -p "Continue."`;

return `(cd "${tempDir}" && ${claudePath} ${args})`;
};

/**
* Build the Claude CLI initial command with the (cd ... && claude ...) pattern
*
Expand Down Expand Up @@ -85,5 +122,6 @@ export const buildClaudeInitialCommand = ({ tempDir, claudePath = 'claude', mode
// Export default object for compatibility
export default {
buildClaudeResumeCommand,
buildClaudeAutonomousResumeCommand,
buildClaudeInitialCommand,
};
34 changes: 23 additions & 11 deletions src/claude.lib.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,24 @@ import { detectUsageLimit, formatUsageLimitMessage } from './usage-limit.lib.mjs
import { createInteractiveHandler } from './interactive-mode.lib.mjs';
import { displayBudgetStats } from './claude.budget-stats.lib.mjs';
// Import Claude command builder for generating resume commands
import { buildClaudeResumeCommand } from './claude.command-builder.lib.mjs';
// Two types of resume commands are supported:
// 1. Interactive resume: Short command that opens interactive mode
// 2. Autonomous resume: Full command with all flags to run autonomously
import { buildClaudeResumeCommand, buildClaudeAutonomousResumeCommand } from './claude.command-builder.lib.mjs';
// Import runtime switch module (extracted to maintain file line limits, see issue #1141)
import { handleClaudeRuntimeSwitch } from './claude.runtime-switch.lib.mjs';

// Helper to display resume command at end of session
// Helper to display resume commands at end of session
// Shows both interactive and autonomous resume options
const showResumeCommand = async (sessionId, tempDir, claudePath, model, log) => {
if (!sessionId || !tempDir) return;
const cmd = buildClaudeResumeCommand({ tempDir, sessionId, claudePath, model });
await log('\n💡 To continue this session in Claude Code interactive mode:\n');
await log(` ${cmd}\n`);
const interactiveCmd = buildClaudeResumeCommand({ tempDir, sessionId, claudePath, model });
const autonomousCmd = buildClaudeAutonomousResumeCommand({ tempDir, sessionId, claudePath, model });
await log('\n💡 To continue this session:\n');
await log(' Interactive mode (opens Claude Code for user interaction):');
await log(` ${interactiveCmd}\n`);
await log(' Autonomous mode (continues work without user interaction):');
await log(` ${autonomousCmd}\n`);
};

/** Format numbers with spaces as thousands separator (no commas) */
Expand Down Expand Up @@ -1083,12 +1091,13 @@ export const executeClaudeCommand = async params => {
limitResetTime = limitInfo.resetTime;
limitTimezone = limitInfo.timezone;

// Format and display user-friendly message
// Format and display user-friendly message with both Claude CLI resume commands
const messageLines = formatUsageLimitMessage({
tool: 'Claude',
resetTime: limitInfo.resetTime,
sessionId,
resumeCommand: argv.url ? `${process.argv[0]} ${process.argv[1]} --auto-continue ${argv.url}` : null,
interactiveResumeCommand: tempDir && sessionId ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : null,
autonomousResumeCommand: tempDir && sessionId ? buildClaudeAutonomousResumeCommand({ tempDir, sessionId, model: argv.model }) : null,
});

for (const line of messageLines) {
Expand All @@ -1098,10 +1107,13 @@ export const executeClaudeCommand = async params => {
await log('\n\n❌ Context length exceeded. Try with a smaller issue or split the work.', { level: 'error' });
} else {
await log(`\n\n❌ Claude command failed with exit code ${exitCode}`, { level: 'error' });
if (sessionId && !argv.resume) {
await log(`📌 Session ID for resuming: ${sessionId}`);
await log('\nTo resume this session, run:');
await log(` ${process.argv[0]} ${process.argv[1]} ${argv.url} --resume ${sessionId}`);
if (sessionId && !argv.resume && tempDir) {
const interactiveCmd = buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model });
const autonomousCmd = buildClaudeAutonomousResumeCommand({ tempDir, sessionId, model: argv.model });
await log(`📌 Session ID: ${sessionId}`);
await log('\n💡 To continue this session:');
await log(` Interactive mode: ${interactiveCmd}`);
await log(` Autonomous mode: ${autonomousCmd}`);
}
}
}
Expand Down
16 changes: 5 additions & 11 deletions src/github.lib.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -454,20 +454,14 @@ The automated solution draft was interrupted because the ${toolName} usage limit
logComment += '\n\n### 🔄 How to Continue\n';

if (limitResetTime) {
logComment += `Once the limit resets at **${limitResetTime}**, `;
logComment += `Once the limit resets at **${limitResetTime}**, you can use the \`--auto-continue-on-limit-reset\` option to automatically resume when the limit resets.`;
} else {
logComment += 'Once the limit resets, ';
logComment += 'You can use the `--auto-continue-on-limit-reset` option to automatically resume when the limit resets.';
}

if (resumeCommand) {
logComment += `you can resume this session by running:
\`\`\`bash
${resumeCommand}
\`\`\``;
} else if (sessionId) {
logComment += `you can resume this session using session ID: \`${sessionId}\``;
} else {
logComment += 'you can retry the operation.';
// Note: The Claude CLI resume command is available in the logs below for manual resumption
if (sessionId) {
logComment += `\n\nSee the logs below for the resume command if you need to continue manually.`;
}

logComment += `
Expand Down
51 changes: 32 additions & 19 deletions src/solve.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const { processAutoContinueForIssue } = autoContinue;
const repository = await import('./solve.repository.lib.mjs');
const { setupTempDirectory, cleanupTempDirectory } = repository;
const results = await import('./solve.results.lib.mjs');
const { cleanupClaudeFile, showSessionSummary, verifyResults, buildClaudeResumeCommand } = results;
const { cleanupClaudeFile, showSessionSummary, verifyResults, buildClaudeResumeCommand, buildClaudeAutonomousResumeCommand } = results;
const claudeLib = await import('./claude.lib.mjs');
const { executeClaude } = claudeLib;

Expand Down Expand Up @@ -927,14 +927,21 @@ try {
await log(`⏰ Limit resets at: ${resetTime}`);
}
await log('');
// Show claude resume command only for --tool claude (or default)
// Uses the (cd ... && claude --resume ...) pattern for a fully copyable, executable command
// Show claude resume commands only for --tool claude (or default)
// Two types of resume commands are provided:
// 1. Interactive resume: Opens Claude Code in interactive mode for user interaction
// 2. Autonomous resume: Continues the work autonomously without user interaction
const toolForResume = argv.tool || 'claude';
if (toolForResume === 'claude') {
const claudeResumeCmd = buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model });
await log('💡 To continue this session in Claude Code interactive mode:');
const interactiveResumeCmd = buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model });
const autonomousResumeCmd = buildClaudeAutonomousResumeCommand({ tempDir, sessionId, model: argv.model });
await log('💡 To continue this session:');
await log('');
await log(` ${claudeResumeCmd}`);
await log(' Interactive mode (opens Claude Code for user interaction):');
await log(` ${interactiveResumeCmd}`);
await log('');
await log(' Autonomous mode (continues work without user interaction):');
await log(` ${autonomousResumeCmd}`);
await log('');
}
}
Expand Down Expand Up @@ -973,12 +980,12 @@ try {
}
} else if (prNumber) {
// Fallback: Post simple failure comment if logs are not attached
// Note: Commands should not be in GitHub comments - only mention the option
try {
const resetTime = global.limitResetTime;
// Build Claude CLI resume command
const tool = argv.tool || 'claude';
const resumeCmd = tool === 'claude' ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : null;
const resumeSection = resumeCmd ? `To resume after the limit resets, use:\n\`\`\`bash\n${resumeCmd}\n\`\`\`` : `Session ID: \`${sessionId}\``;
// Note: Commands should not be in GitHub comments - only mention the option
// The resume command is available in the logs (collapsed block or gist link) for advanced users
const resumeSection = sessionId ? `Session ID: \`${sessionId}\`\n\nUse the \`--auto-continue-on-limit-reset\` option to automatically resume when the limit resets.` : 'Use the `--auto-continue-on-limit-reset` option to automatically resume when the limit resets.';
// Format the reset time with relative time and UTC conversion if available
const timezone = global.limitTimezone || null;
const formattedResetTime = resetTime ? formatResetTimeWithRelative(resetTime, timezone) : null;
Expand Down Expand Up @@ -1048,10 +1055,9 @@ try {
return `${days}:${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
};

// Build Claude CLI resume command
const tool = argv.tool || 'claude';
const resumeCmd = tool === 'claude' ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : null;
const waitingComment = `⏳ **Usage Limit Reached - Waiting to Continue**\n\nThe AI tool has reached its usage limit. Auto-resume is enabled with \`--auto-resume-on-limit-reset\`.\n\n**Reset time:** ${global.limitResetTime}\n**Wait time:** ${formatWaitTime(waitMs)} (days:hours:minutes:seconds)\n\nThe session will automatically resume when the limit resets.\n\nSession ID: \`${sessionId}\`${resumeCmd ? `\n\nTo resume manually:\n\`\`\`bash\n${resumeCmd}\n\`\`\`` : ''}`;
// Note: Commands should not be in GitHub comments - only mention the option
// The resume command is available in the logs (collapsed block or gist link) for advanced users
const waitingComment = `⏳ **Usage Limit Reached - Waiting to Continue**\n\nThe AI tool has reached its usage limit. Auto-resume is enabled with \`--auto-resume-on-limit-reset\`.\n\n**Reset time:** ${global.limitResetTime}\n**Wait time:** ${formatWaitTime(waitMs)} (days:hours:minutes:seconds)\n\nThe session will automatically resume when the limit resets.\n\nSession ID: \`${sessionId}\``;

const commentResult = await $`gh pr comment ${prNumber} --repo ${owner}/${repo} --body ${waitingComment}`;
if (commentResult.code === 0) {
Expand All @@ -1070,15 +1076,22 @@ try {
const shouldSkipFailureExitForAutoLimitContinue = limitReached && argv.autoResumeOnLimitReset;

if (!success && !shouldSkipFailureExitForAutoLimitContinue) {
// Show claude resume command only for --tool claude (or default) on failure
// Uses the (cd ... && claude --resume ...) pattern for a fully copyable, executable command
// Show claude resume commands only for --tool claude (or default) on failure
// Two types of resume commands are provided:
// 1. Interactive resume: Opens Claude Code in interactive mode for user interaction
// 2. Autonomous resume: Continues the work autonomously without user interaction
const toolForFailure = argv.tool || 'claude';
if (sessionId && toolForFailure === 'claude') {
const claudeResumeCmd = buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model });
const interactiveResumeCmd = buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model });
const autonomousResumeCmd = buildClaudeAutonomousResumeCommand({ tempDir, sessionId, model: argv.model });
await log('');
await log('💡 To continue this session:');
await log('');
await log('💡 To continue this session in Claude Code interactive mode:');
await log(' Interactive mode (opens Claude Code for user interaction):');
await log(` ${interactiveResumeCmd}`);
await log('');
await log(` ${claudeResumeCmd}`);
await log(' Autonomous mode (continues work without user interaction):');
await log(` ${autonomousResumeCmd}`);
await log('');
}

Expand Down
24 changes: 17 additions & 7 deletions src/solve.results.lib.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@ const { autoContinueWhenLimitResets } = autoContinue;
// Import Claude-specific command builders
// These are used to generate copy-pasteable Claude CLI resume commands for users
// Pattern: (cd "/tmp/gh-issue-solver-..." && claude --resume <session-id>)
// Two types of resume commands are supported:
// 1. Interactive resume: Short command that opens interactive mode
// 2. Autonomous resume: Full command with all flags to run autonomously
const claudeCommandBuilder = await import('./claude.command-builder.lib.mjs');
export const { buildClaudeResumeCommand, buildClaudeInitialCommand } = claudeCommandBuilder;
export const { buildClaudeResumeCommand, buildClaudeAutonomousResumeCommand, buildClaudeInitialCommand } = claudeCommandBuilder;

// Import error handling functions
// const errorHandlers = await import('./solve.error-handlers.lib.mjs'); // Not currently used
Expand Down Expand Up @@ -357,18 +360,25 @@ export const showSessionSummary = async (sessionId, limitReached, argv, issueUrl
const absoluteLogPath = path.resolve(getLogFile());
await log(`✅ Complete log file: ${absoluteLogPath}`);

// Show claude resume command only for --tool claude (or default)
// Show claude resume commands only for --tool claude (or default)
// This allows users to investigate, resume, see context, and more
// Uses the (cd ... && claude --resume ...) pattern for a fully copyable, executable command
// Two types of resume commands are provided:
// 1. Interactive resume: Opens Claude Code in interactive mode for user interaction
// 2. Autonomous resume: Continues the work autonomously without user interaction
const tool = argv.tool || 'claude';
if (tool === 'claude') {
// Build the Claude CLI resume command using the command builder
const claudeResumeCmd = buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model });
// Build both resume commands using the command builder
const interactiveResumeCmd = buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model });
const autonomousResumeCmd = buildClaudeAutonomousResumeCommand({ tempDir, sessionId, model: argv.model });

await log('');
await log('💡 To continue this session in Claude Code interactive mode:');
await log('💡 To continue this session:');
await log('');
await log(` ${claudeResumeCmd}`);
await log(' Interactive mode (opens Claude Code for user interaction):');
await log(` ${interactiveResumeCmd}`);
await log('');
await log(' Autonomous mode (continues work without user interaction):');
await log(` ${autonomousResumeCmd}`);
await log('');
}

Expand Down
24 changes: 19 additions & 5 deletions src/usage-limit.lib.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -382,10 +382,12 @@ export function formatResetTimeWithRelative(resetTime, timezone = null) {
* @param {string} options.tool - Tool name (claude, codex, opencode)
* @param {string|null} options.resetTime - Time when limit resets
* @param {string|null} options.sessionId - Session ID for resuming
* @param {string|null} options.resumeCommand - Command to resume session
* @param {string|null} options.resumeCommand - Interactive resume command (legacy, for backward compatibility)
* @param {string|null} options.interactiveResumeCommand - Command to resume in interactive mode
* @param {string|null} options.autonomousResumeCommand - Command to resume in autonomous mode
* @returns {string[]} - Array of formatted message lines
*/
export function formatUsageLimitMessage({ tool, resetTime, sessionId, resumeCommand }) {
export function formatUsageLimitMessage({ tool, resetTime, sessionId, resumeCommand, interactiveResumeCommand, autonomousResumeCommand }) {
const lines = ['', '⏳ Usage Limit Reached!', '', `Your ${tool || 'AI tool'} usage limit has been reached.`];

if (resetTime) {
Expand All @@ -394,12 +396,24 @@ export function formatUsageLimitMessage({ tool, resetTime, sessionId, resumeComm
lines.push('Please wait for the limit to reset.');
}

if (sessionId && resumeCommand) {
// Support both new (dual command) and legacy (single command) formats
const interactiveCmd = interactiveResumeCommand || resumeCommand;

if (sessionId && (interactiveCmd || autonomousResumeCommand)) {
lines.push('');
lines.push(`📌 Session ID: ${sessionId}`);
lines.push('');
lines.push('To resume this session after the limit resets, run:');
lines.push(` ${resumeCommand}`);
lines.push('To resume this session after the limit resets:');
if (interactiveCmd) {
lines.push('');
lines.push(' Interactive mode (opens Claude Code for user interaction):');
lines.push(` ${interactiveCmd}`);
}
if (autonomousResumeCommand) {
lines.push('');
lines.push(' Autonomous mode (continues work without user interaction):');
lines.push(` ${autonomousResumeCommand}`);
}
}

lines.push('');
Expand Down
Loading