mirror of
https://github.com/andrepimenta/claude-code-chat.git
synced 2025-12-09 10:59:53 +00:00
Improve process termination and update diff icon colors
- Add AbortController for clean process management - Add _killProcessGroup() with platform-specific handling (Unix/Windows/WSL) - Add _killClaudeProcess() with proper SIGTERM→wait→SIGKILL flow - Update spawn options with detached and signal support - Handle WSL specially with pkill inside the distro - Update Open Diff button icon to pastel colors 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
150
src/extension.ts
150
src/extension.ts
@@ -140,6 +140,9 @@ class ClaudeChatProvider {
|
||||
lastUserMessage: string
|
||||
}> = [];
|
||||
private _currentClaudeProcess: cp.ChildProcess | undefined;
|
||||
private _abortController: AbortController | undefined;
|
||||
private _isWslProcess: boolean = false;
|
||||
private _wslDistro: string = 'Ubuntu';
|
||||
private _selectedModel: string = 'default'; // Default model
|
||||
private _isProcessing: boolean | undefined;
|
||||
private _draftMessage: string = '';
|
||||
@@ -536,12 +539,21 @@ class ClaudeChatProvider {
|
||||
|
||||
let claudeProcess: cp.ChildProcess;
|
||||
|
||||
// Create new AbortController for this request
|
||||
this._abortController = new AbortController();
|
||||
|
||||
if (wslEnabled) {
|
||||
// Use WSL with bash -ic for proper environment loading
|
||||
console.log('Using WSL configuration:', { wslDistro, nodePath, claudePath });
|
||||
const wslCommand = `"${nodePath}" --no-warnings --enable-source-maps "${claudePath}" ${args.join(' ')}`;
|
||||
|
||||
// Track WSL state for proper process termination
|
||||
this._isWslProcess = true;
|
||||
this._wslDistro = wslDistro;
|
||||
|
||||
claudeProcess = cp.spawn('wsl', ['-d', wslDistro, 'bash', '-ic', wslCommand], {
|
||||
signal: this._abortController.signal,
|
||||
detached: process.platform !== 'win32',
|
||||
cwd: cwd,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
env: {
|
||||
@@ -551,10 +563,15 @@ class ClaudeChatProvider {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Not using WSL
|
||||
this._isWslProcess = false;
|
||||
|
||||
// Use native claude command
|
||||
console.log('Using native Claude command');
|
||||
claudeProcess = cp.spawn('claude', args, {
|
||||
signal: this._abortController.signal,
|
||||
shell: process.platform === 'win32',
|
||||
detached: process.platform !== 'win32',
|
||||
cwd: cwd,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
env: {
|
||||
@@ -979,9 +996,8 @@ class ClaudeChatProvider {
|
||||
}
|
||||
|
||||
|
||||
private _newSession() {
|
||||
|
||||
this._isProcessing = false
|
||||
private async _newSession(): Promise<void> {
|
||||
this._isProcessing = false;
|
||||
|
||||
// Update UI state
|
||||
this._postMessage({
|
||||
@@ -989,12 +1005,8 @@ class ClaudeChatProvider {
|
||||
data: { isProcessing: false }
|
||||
});
|
||||
|
||||
// Try graceful termination first
|
||||
if (this._currentClaudeProcess) {
|
||||
const processToKill = this._currentClaudeProcess;
|
||||
this._currentClaudeProcess = undefined;
|
||||
processToKill.kill('SIGTERM');
|
||||
}
|
||||
// Kill Claude process and all child processes
|
||||
await this._killClaudeProcess();
|
||||
|
||||
// Clear current session
|
||||
this._currentSessionId = undefined;
|
||||
@@ -2094,10 +2106,88 @@ class ClaudeChatProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private _stopClaudeProcess(): void {
|
||||
private async _killProcessGroup(pid: number, signal: string = 'SIGTERM'): Promise<void> {
|
||||
if (this._isWslProcess) {
|
||||
// WSL: Kill processes inside WSL using pkill
|
||||
// The Windows PID won't work inside WSL, so we kill by name
|
||||
try {
|
||||
// Kill all node/claude processes started by this session inside WSL
|
||||
const killSignal = signal === 'SIGKILL' ? '-9' : '-15';
|
||||
await exec(`wsl -d ${this._wslDistro} pkill ${killSignal} -f "claude"`);
|
||||
} catch {
|
||||
// Process may already be dead or pkill not available
|
||||
}
|
||||
// Also kill the Windows-side wsl process
|
||||
try {
|
||||
await exec(`taskkill /pid ${pid} /t /f`);
|
||||
} catch {
|
||||
// Process may already be dead
|
||||
}
|
||||
} else if (process.platform === 'win32') {
|
||||
// Windows: Use taskkill with /T flag for tree kill
|
||||
try {
|
||||
await exec(`taskkill /pid ${pid} /t /f`);
|
||||
} catch {
|
||||
// Process may already be dead
|
||||
}
|
||||
} else {
|
||||
// Unix: Kill process group with negative PID
|
||||
try {
|
||||
process.kill(-pid, signal as NodeJS.Signals);
|
||||
} catch {
|
||||
// Process may already be dead
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _killClaudeProcess(): Promise<void> {
|
||||
const processToKill = this._currentClaudeProcess;
|
||||
const pid = processToKill?.pid;
|
||||
|
||||
// 1. Abort via controller (clean API)
|
||||
this._abortController?.abort();
|
||||
this._abortController = undefined;
|
||||
|
||||
// 2. Clear reference immediately
|
||||
this._currentClaudeProcess = undefined;
|
||||
|
||||
if (!pid) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Terminating Claude process group (PID: ${pid})...`);
|
||||
|
||||
// 3. Kill process group (handles children)
|
||||
await this._killProcessGroup(pid, 'SIGTERM');
|
||||
|
||||
// 4. Wait for process to exit, with timeout
|
||||
const exitPromise = new Promise<void>((resolve) => {
|
||||
if (processToKill?.killed) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
processToKill?.once('exit', () => resolve());
|
||||
});
|
||||
|
||||
const timeoutPromise = new Promise<void>((resolve) => {
|
||||
setTimeout(() => resolve(), 2000);
|
||||
});
|
||||
|
||||
await Promise.race([exitPromise, timeoutPromise]);
|
||||
|
||||
// 5. Force kill if still running
|
||||
if (processToKill && !processToKill.killed) {
|
||||
console.log(`Force killing Claude process group (PID: ${pid})...`);
|
||||
await this._killProcessGroup(pid, 'SIGKILL');
|
||||
}
|
||||
|
||||
console.log('Claude process group terminated');
|
||||
}
|
||||
|
||||
private async _stopClaudeProcess(): Promise<void> {
|
||||
console.log('Stop request received');
|
||||
|
||||
this._isProcessing = false
|
||||
this._isProcessing = false;
|
||||
|
||||
// Update UI state
|
||||
this._postMessage({
|
||||
@@ -2105,37 +2195,17 @@ class ClaudeChatProvider {
|
||||
data: { isProcessing: false }
|
||||
});
|
||||
|
||||
if (this._currentClaudeProcess) {
|
||||
console.log('Terminating Claude process...');
|
||||
await this._killClaudeProcess();
|
||||
|
||||
// Try graceful termination first
|
||||
this._currentClaudeProcess.kill('SIGTERM');
|
||||
this._postMessage({
|
||||
type: 'clearLoading'
|
||||
});
|
||||
|
||||
// Force kill after 2 seconds if still running
|
||||
setTimeout(() => {
|
||||
if (this._currentClaudeProcess && !this._currentClaudeProcess.killed) {
|
||||
console.log('Force killing Claude process...');
|
||||
this._currentClaudeProcess.kill('SIGKILL');
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
// Clear process reference
|
||||
this._currentClaudeProcess = undefined;
|
||||
|
||||
this._postMessage({
|
||||
type: 'clearLoading'
|
||||
});
|
||||
|
||||
// Send stop confirmation message directly to UI and save
|
||||
this._sendAndSaveMessage({
|
||||
type: 'error',
|
||||
data: '⏹️ Claude code was stopped.'
|
||||
});
|
||||
|
||||
console.log('Claude process termination initiated');
|
||||
} else {
|
||||
console.log('No Claude process running to stop');
|
||||
}
|
||||
// Send stop confirmation message directly to UI and save
|
||||
this._sendAndSaveMessage({
|
||||
type: 'error',
|
||||
data: '⏹️ Claude code was stopped.'
|
||||
});
|
||||
}
|
||||
|
||||
private _updateConversationIndex(filename: string, conversationData: ConversationData): void {
|
||||
|
||||
@@ -614,7 +614,7 @@ const getScript = (isTelemetryEnabled: boolean) => `<script>
|
||||
html += '<span class="diff-summary">Summary: ' + summary + '</span>';
|
||||
if (showButton) {
|
||||
html += '<button class="diff-open-btn" onclick="openDiffEditor()" title="Open side-by-side diff in VS Code">';
|
||||
html += '<svg width="14" height="14" viewBox="0 0 16 16"><rect x="1" y="1" width="6" height="14" rx="1" fill="none" stroke="currentColor" stroke-opacity="0.5"/><rect x="9" y="1" width="6" height="14" rx="1" fill="none" stroke="currentColor" stroke-opacity="0.5"/><line x1="2.5" y1="4" x2="5.5" y2="4" stroke="#f85149" stroke-width="1.5"/><line x1="2.5" y1="7" x2="5.5" y2="7" stroke="currentColor" stroke-opacity="0.4" stroke-width="1.5"/><line x1="2.5" y1="10" x2="5.5" y2="10" stroke="currentColor" stroke-opacity="0.4" stroke-width="1.5"/><line x1="10.5" y1="4" x2="13.5" y2="4" stroke="currentColor" stroke-opacity="0.4" stroke-width="1.5"/><line x1="10.5" y1="7" x2="13.5" y2="7" stroke="#3fb950" stroke-width="1.5"/><line x1="10.5" y1="10" x2="13.5" y2="10" stroke="#3fb950" stroke-width="1.5"/></svg>';
|
||||
html += '<svg width="14" height="14" viewBox="0 0 16 16"><rect x="1" y="1" width="6" height="14" rx="1" fill="none" stroke="currentColor" stroke-opacity="0.5"/><rect x="9" y="1" width="6" height="14" rx="1" fill="none" stroke="currentColor" stroke-opacity="0.5"/><line x1="2.5" y1="4" x2="5.5" y2="4" stroke="#e8a0a0" stroke-width="1.5"/><line x1="2.5" y1="7" x2="5.5" y2="7" stroke="currentColor" stroke-opacity="0.4" stroke-width="1.5"/><line x1="2.5" y1="10" x2="5.5" y2="10" stroke="currentColor" stroke-opacity="0.4" stroke-width="1.5"/><line x1="10.5" y1="4" x2="13.5" y2="4" stroke="currentColor" stroke-opacity="0.4" stroke-width="1.5"/><line x1="10.5" y1="7" x2="13.5" y2="7" stroke="#8fd48f" stroke-width="1.5"/><line x1="10.5" y1="10" x2="13.5" y2="10" stroke="#8fd48f" stroke-width="1.5"/></svg>';
|
||||
html += 'Open Diff</button>';
|
||||
}
|
||||
html += '</div>';
|
||||
@@ -713,7 +713,7 @@ const getScript = (isTelemetryEnabled: boolean) => `<script>
|
||||
html += '<span class="diff-summary">Summary: ' + input.edits.length + ' edit' + (input.edits.length > 1 ? 's' : '') + '</span>';
|
||||
if (showButton) {
|
||||
html += '<button class="diff-open-btn" onclick="openDiffEditor()" title="Open side-by-side diff in VS Code">';
|
||||
html += '<svg width="14" height="14" viewBox="0 0 16 16"><rect x="1" y="1" width="6" height="14" rx="1" fill="none" stroke="currentColor" stroke-opacity="0.5"/><rect x="9" y="1" width="6" height="14" rx="1" fill="none" stroke="currentColor" stroke-opacity="0.5"/><line x1="2.5" y1="4" x2="5.5" y2="4" stroke="#f85149" stroke-width="1.5"/><line x1="2.5" y1="7" x2="5.5" y2="7" stroke="currentColor" stroke-opacity="0.4" stroke-width="1.5"/><line x1="2.5" y1="10" x2="5.5" y2="10" stroke="currentColor" stroke-opacity="0.4" stroke-width="1.5"/><line x1="10.5" y1="4" x2="13.5" y2="4" stroke="currentColor" stroke-opacity="0.4" stroke-width="1.5"/><line x1="10.5" y1="7" x2="13.5" y2="7" stroke="#3fb950" stroke-width="1.5"/><line x1="10.5" y1="10" x2="13.5" y2="10" stroke="#3fb950" stroke-width="1.5"/></svg>';
|
||||
html += '<svg width="14" height="14" viewBox="0 0 16 16"><rect x="1" y="1" width="6" height="14" rx="1" fill="none" stroke="currentColor" stroke-opacity="0.5"/><rect x="9" y="1" width="6" height="14" rx="1" fill="none" stroke="currentColor" stroke-opacity="0.5"/><line x1="2.5" y1="4" x2="5.5" y2="4" stroke="#e8a0a0" stroke-width="1.5"/><line x1="2.5" y1="7" x2="5.5" y2="7" stroke="currentColor" stroke-opacity="0.4" stroke-width="1.5"/><line x1="2.5" y1="10" x2="5.5" y2="10" stroke="currentColor" stroke-opacity="0.4" stroke-width="1.5"/><line x1="10.5" y1="4" x2="13.5" y2="4" stroke="currentColor" stroke-opacity="0.4" stroke-width="1.5"/><line x1="10.5" y1="7" x2="13.5" y2="7" stroke="#8fd48f" stroke-width="1.5"/><line x1="10.5" y1="10" x2="13.5" y2="10" stroke="#8fd48f" stroke-width="1.5"/></svg>';
|
||||
html += 'Open Diff</button>';
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
Reference in New Issue
Block a user