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:
andrepimenta
2025-12-02 16:33:27 +00:00
parent 82899ebb40
commit 0764bf8202
2 changed files with 112 additions and 42 deletions

View File

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

View File

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