mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
Suppress socket disconnect error on normal Claude Code exit
- Add isExitingNormally flag set during onExit callback - Check for common normal disconnect errors (EPIPE, ECONNRESET, Unknown error) - Log as debug instead of error for normal disconnects - Prevents misleading error message when exiting Claude Code normally
This commit is contained in:
parent
7a69ccb711
commit
5d49693573
3 changed files with 40 additions and 17 deletions
|
|
@ -1,8 +1,8 @@
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { DEFAULT_NOTIFICATION_PREFERENCES } from '../../types/config.js';
|
import { DEFAULT_NOTIFICATION_PREFERENCES } from '../../types/config.js';
|
||||||
|
import { notificationEventService } from './notification-event-service.js';
|
||||||
import type { NotificationPreferences } from './push-notification-service.js';
|
import type { NotificationPreferences } from './push-notification-service.js';
|
||||||
import { pushNotificationService } from './push-notification-service.js';
|
import { pushNotificationService } from './push-notification-service.js';
|
||||||
import { notificationEventService } from './notification-event-service.js';
|
|
||||||
import { serverConfigService } from './server-config-service.js';
|
import { serverConfigService } from './server-config-service.js';
|
||||||
|
|
||||||
// Mock notification event service
|
// Mock notification event service
|
||||||
|
|
@ -514,7 +514,9 @@ describe('PushNotificationService', () => {
|
||||||
vibrationEnabled: true,
|
vibrationEnabled: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
(serverConfigService.updateNotificationPreferences as vi.Mock).mockRejectedValue(new Error('Network error'));
|
(serverConfigService.updateNotificationPreferences as vi.Mock).mockRejectedValue(
|
||||||
|
new Error('Network error')
|
||||||
|
);
|
||||||
|
|
||||||
await expect(pushNotificationService.savePreferences(preferences)).rejects.toThrow(
|
await expect(pushNotificationService.savePreferences(preferences)).rejects.toThrow(
|
||||||
'Network error'
|
'Network error'
|
||||||
|
|
@ -543,7 +545,7 @@ describe('PushNotificationService', () => {
|
||||||
const testPromise = pushNotificationService.testNotification();
|
const testPromise = pushNotificationService.testNotification();
|
||||||
|
|
||||||
// Simulate receiving the event
|
// Simulate receiving the event
|
||||||
await new Promise(resolve => setTimeout(resolve, 100)); // allow time for listener to be registered
|
await new Promise((resolve) => setTimeout(resolve, 100)); // allow time for listener to be registered
|
||||||
|
|
||||||
expect(testNotificationHandler!).toBeDefined();
|
expect(testNotificationHandler!).toBeDefined();
|
||||||
testNotificationHandler!({
|
testNotificationHandler!({
|
||||||
|
|
@ -584,7 +586,7 @@ describe('PushNotificationService', () => {
|
||||||
const testPromise = pushNotificationService.testNotification();
|
const testPromise = pushNotificationService.testNotification();
|
||||||
|
|
||||||
// Simulate receiving the event
|
// Simulate receiving the event
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
|
||||||
expect(testNotificationHandler!).toBeDefined();
|
expect(testNotificationHandler!).toBeDefined();
|
||||||
testNotificationHandler!({});
|
testNotificationHandler!({});
|
||||||
|
|
|
||||||
|
|
@ -394,16 +394,13 @@ export class PushNotificationService {
|
||||||
|
|
||||||
// Show notification if we have permission
|
// Show notification if we have permission
|
||||||
if (this.serviceWorkerRegistration && this.getPermission() === 'granted') {
|
if (this.serviceWorkerRegistration && this.getPermission() === 'granted') {
|
||||||
await this.serviceWorkerRegistration.showNotification(
|
await this.serviceWorkerRegistration.showNotification(data.title || 'VibeTunnel Test', {
|
||||||
data.title || 'VibeTunnel Test',
|
body: data.body || 'Test notification received via SSE!',
|
||||||
{
|
icon: '/apple-touch-icon.png',
|
||||||
body: data.body || 'Test notification received via SSE!',
|
badge: '/favicon-32.png',
|
||||||
icon: '/apple-touch-icon.png',
|
tag: 'vibetunnel-test-sse',
|
||||||
badge: '/favicon-32.png',
|
requireInteraction: false,
|
||||||
tag: 'vibetunnel-test-sse',
|
});
|
||||||
requireInteraction: false,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
logger.log('✅ Displayed SSE test notification');
|
logger.log('✅ Displayed SSE test notification');
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
|
|
|
||||||
|
|
@ -390,6 +390,7 @@ export async function startVibeTunnelForward(args: string[]) {
|
||||||
// Variables that need to be accessible in cleanup
|
// Variables that need to be accessible in cleanup
|
||||||
let sessionFileWatcher: fs.FSWatcher | undefined;
|
let sessionFileWatcher: fs.FSWatcher | undefined;
|
||||||
let fileWatchDebounceTimer: NodeJS.Timeout | undefined;
|
let fileWatchDebounceTimer: NodeJS.Timeout | undefined;
|
||||||
|
let isExitingNormally = false;
|
||||||
|
|
||||||
const sessionOptions: Parameters<typeof ptyManager.createSession>[1] = {
|
const sessionOptions: Parameters<typeof ptyManager.createSession>[1] = {
|
||||||
sessionId: finalSessionId,
|
sessionId: finalSessionId,
|
||||||
|
|
@ -405,6 +406,9 @@ export async function startVibeTunnelForward(args: string[]) {
|
||||||
gitIsWorktree: gitInfo.gitIsWorktree,
|
gitIsWorktree: gitInfo.gitIsWorktree,
|
||||||
gitMainRepoPath: gitInfo.gitMainRepoPath,
|
gitMainRepoPath: gitInfo.gitMainRepoPath,
|
||||||
onExit: async (exitCode: number) => {
|
onExit: async (exitCode: number) => {
|
||||||
|
// Mark that we're exiting normally
|
||||||
|
isExitingNormally = true;
|
||||||
|
|
||||||
// Show exit message
|
// Show exit message
|
||||||
logger.log(
|
logger.log(
|
||||||
chalk.yellow(`\n✓ VibeTunnel session ended`) + chalk.gray(` (exit code: ${exitCode})`)
|
chalk.yellow(`\n✓ VibeTunnel session ended`) + chalk.gray(` (exit code: ${exitCode})`)
|
||||||
|
|
@ -737,7 +741,27 @@ export async function startVibeTunnelForward(args: string[]) {
|
||||||
|
|
||||||
// Handle socket events
|
// Handle socket events
|
||||||
socketClient.on('disconnect', (error) => {
|
socketClient.on('disconnect', (error) => {
|
||||||
logger.error('Socket disconnected:', error?.message || 'Unknown error');
|
// Don't log error if we're exiting normally
|
||||||
|
if (isExitingNormally) {
|
||||||
|
logger.debug('Socket disconnected during normal exit');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is a common disconnect error during normal operation
|
||||||
|
const errorMessage = error?.message || '';
|
||||||
|
const isNormalDisconnect =
|
||||||
|
errorMessage.includes('EPIPE') ||
|
||||||
|
errorMessage.includes('ECONNRESET') ||
|
||||||
|
errorMessage.includes('socket hang up') ||
|
||||||
|
errorMessage === 'Unknown error' || // Common during clean exits
|
||||||
|
!error; // No error object means clean disconnect
|
||||||
|
|
||||||
|
if (isNormalDisconnect) {
|
||||||
|
logger.debug('Socket disconnected (normal termination)');
|
||||||
|
} else {
|
||||||
|
logger.error('Socket disconnected:', error?.message || 'Unknown error');
|
||||||
|
}
|
||||||
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue