mirror of
https://github.com/andrepimenta/claude-code-chat.git
synced 2025-12-11 20:39:43 +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
|
lastUserMessage: string
|
||||||
}> = [];
|
}> = [];
|
||||||
private _currentClaudeProcess: cp.ChildProcess | undefined;
|
private _currentClaudeProcess: cp.ChildProcess | undefined;
|
||||||
|
private _abortController: AbortController | undefined;
|
||||||
|
private _isWslProcess: boolean = false;
|
||||||
|
private _wslDistro: string = 'Ubuntu';
|
||||||
private _selectedModel: string = 'default'; // Default model
|
private _selectedModel: string = 'default'; // Default model
|
||||||
private _isProcessing: boolean | undefined;
|
private _isProcessing: boolean | undefined;
|
||||||
private _draftMessage: string = '';
|
private _draftMessage: string = '';
|
||||||
@@ -536,12 +539,21 @@ class ClaudeChatProvider {
|
|||||||
|
|
||||||
let claudeProcess: cp.ChildProcess;
|
let claudeProcess: cp.ChildProcess;
|
||||||
|
|
||||||
|
// Create new AbortController for this request
|
||||||
|
this._abortController = new AbortController();
|
||||||
|
|
||||||
if (wslEnabled) {
|
if (wslEnabled) {
|
||||||
// Use WSL with bash -ic for proper environment loading
|
// Use WSL with bash -ic for proper environment loading
|
||||||
console.log('Using WSL configuration:', { wslDistro, nodePath, claudePath });
|
console.log('Using WSL configuration:', { wslDistro, nodePath, claudePath });
|
||||||
const wslCommand = `"${nodePath}" --no-warnings --enable-source-maps "${claudePath}" ${args.join(' ')}`;
|
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], {
|
claudeProcess = cp.spawn('wsl', ['-d', wslDistro, 'bash', '-ic', wslCommand], {
|
||||||
|
signal: this._abortController.signal,
|
||||||
|
detached: process.platform !== 'win32',
|
||||||
cwd: cwd,
|
cwd: cwd,
|
||||||
stdio: ['pipe', 'pipe', 'pipe'],
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
env: {
|
env: {
|
||||||
@@ -551,10 +563,15 @@ class ClaudeChatProvider {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// Not using WSL
|
||||||
|
this._isWslProcess = false;
|
||||||
|
|
||||||
// Use native claude command
|
// Use native claude command
|
||||||
console.log('Using native Claude command');
|
console.log('Using native Claude command');
|
||||||
claudeProcess = cp.spawn('claude', args, {
|
claudeProcess = cp.spawn('claude', args, {
|
||||||
|
signal: this._abortController.signal,
|
||||||
shell: process.platform === 'win32',
|
shell: process.platform === 'win32',
|
||||||
|
detached: process.platform !== 'win32',
|
||||||
cwd: cwd,
|
cwd: cwd,
|
||||||
stdio: ['pipe', 'pipe', 'pipe'],
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
env: {
|
env: {
|
||||||
@@ -979,9 +996,8 @@ class ClaudeChatProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private _newSession() {
|
private async _newSession(): Promise<void> {
|
||||||
|
this._isProcessing = false;
|
||||||
this._isProcessing = false
|
|
||||||
|
|
||||||
// Update UI state
|
// Update UI state
|
||||||
this._postMessage({
|
this._postMessage({
|
||||||
@@ -989,12 +1005,8 @@ class ClaudeChatProvider {
|
|||||||
data: { isProcessing: false }
|
data: { isProcessing: false }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Try graceful termination first
|
// Kill Claude process and all child processes
|
||||||
if (this._currentClaudeProcess) {
|
await this._killClaudeProcess();
|
||||||
const processToKill = this._currentClaudeProcess;
|
|
||||||
this._currentClaudeProcess = undefined;
|
|
||||||
processToKill.kill('SIGTERM');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear current session
|
// Clear current session
|
||||||
this._currentSessionId = undefined;
|
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');
|
console.log('Stop request received');
|
||||||
|
|
||||||
this._isProcessing = false
|
this._isProcessing = false;
|
||||||
|
|
||||||
// Update UI state
|
// Update UI state
|
||||||
this._postMessage({
|
this._postMessage({
|
||||||
@@ -2105,37 +2195,17 @@ class ClaudeChatProvider {
|
|||||||
data: { isProcessing: false }
|
data: { isProcessing: false }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this._currentClaudeProcess) {
|
await this._killClaudeProcess();
|
||||||
console.log('Terminating Claude process...');
|
|
||||||
|
|
||||||
// Try graceful termination first
|
this._postMessage({
|
||||||
this._currentClaudeProcess.kill('SIGTERM');
|
type: 'clearLoading'
|
||||||
|
});
|
||||||
|
|
||||||
// Force kill after 2 seconds if still running
|
// Send stop confirmation message directly to UI and save
|
||||||
setTimeout(() => {
|
this._sendAndSaveMessage({
|
||||||
if (this._currentClaudeProcess && !this._currentClaudeProcess.killed) {
|
type: 'error',
|
||||||
console.log('Force killing Claude process...');
|
data: '⏹️ Claude code was stopped.'
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _updateConversationIndex(filename: string, conversationData: ConversationData): void {
|
private _updateConversationIndex(filename: string, conversationData: ConversationData): void {
|
||||||
|
|||||||
@@ -614,7 +614,7 @@ const getScript = (isTelemetryEnabled: boolean) => `<script>
|
|||||||
html += '<span class="diff-summary">Summary: ' + summary + '</span>';
|
html += '<span class="diff-summary">Summary: ' + summary + '</span>';
|
||||||
if (showButton) {
|
if (showButton) {
|
||||||
html += '<button class="diff-open-btn" onclick="openDiffEditor()" title="Open side-by-side diff in VS Code">';
|
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 += 'Open Diff</button>';
|
||||||
}
|
}
|
||||||
html += '</div>';
|
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>';
|
html += '<span class="diff-summary">Summary: ' + input.edits.length + ' edit' + (input.edits.length > 1 ? 's' : '') + '</span>';
|
||||||
if (showButton) {
|
if (showButton) {
|
||||||
html += '<button class="diff-open-btn" onclick="openDiffEditor()" title="Open side-by-side diff in VS Code">';
|
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 += 'Open Diff</button>';
|
||||||
}
|
}
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|||||||
Reference in New Issue
Block a user