Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 77 additions & 8 deletions apps/frontend/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { config } from 'dotenv';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import { existsSync } from 'fs';
import { homedir } from 'os';

// ESM-compatible __dirname
const __filename = fileURLToPath(import.meta.url);
Expand All @@ -37,7 +38,7 @@ for (const envPath of possibleEnvPaths) {

import { app, BrowserWindow, shell, nativeImage, session, screen } from 'electron';
import { join } from 'path';
import { accessSync, readFileSync, writeFileSync, rmSync } from 'fs';
import { accessSync, readFileSync, writeFileSync, rmSync, mkdirSync } from 'fs';
import { electronApp, optimizer, is } from '@electron-toolkit/utils';
import { setupIpcHandlers } from './ipc-setup';
import { AgentManager } from './agent';
Expand All @@ -52,6 +53,7 @@ import { setupErrorLogging } from './app-logger';
import { initSentryMain } from './sentry';
import { preWarmToolCache } from './cli-tool-manager';
import { initializeClaudeProfileManager } from './claude-profile-manager';
import { isWindows, isMacOS } from './platform';
import type { AppSettings } from '../shared/types';

// ─────────────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -112,6 +114,69 @@ function cleanupStaleUpdateMetadata(): void {
}
}

/**
* Fix corrupted or empty known_marketplaces.json in Claude CLI config.
*
* On Windows, an empty or array-containing known_marketplaces.json file causes
* Claude CLI crashes. This file must contain a valid JSON object (at minimum `{}`).
*
* This fix runs before any Claude CLI calls to prevent crashes.
*/
function fixKnownMarketplacesJson(): void {
if (!isWindows()) {
return;
}

const pluginsDir = join(homedir(), '.claude', 'plugins');
const marketplacesPath = join(pluginsDir, 'known_marketplaces.json');

// Ensure plugins directory exists (mkdirSync with recursive is idempotent)
try {
mkdirSync(pluginsDir, { recursive: true });
} catch (e) {
console.warn('[main] Failed to create plugins directory:', e);
return;
}

// Check if file needs fixing by trying to read it directly
// (avoids TOCTOU race condition from existsSync + readFileSync)
let needsFix = false;
try {
const content = readFileSync(marketplacesPath, 'utf-8').trim();
// Fix if empty, contains only whitespace, or is an array instead of object
if (content === '' || content === '[]') {
needsFix = true;
console.log('[main] Detected corrupted known_marketplaces.json:', content || '(empty)');
} else {
// Validate it's a proper JSON object (not array)
const parsed = JSON.parse(content);
if (Array.isArray(parsed)) {
needsFix = true;
console.log('[main] known_marketplaces.json contains array instead of object');
}
}
} catch (e) {
// File doesn't exist or can't be read/parsed - create it
needsFix = true;
const errCode = (e as NodeJS.ErrnoException).code;
if (errCode !== 'ENOENT') {
console.warn('[main] Failed to read known_marketplaces.json:', e);
}
}

if (needsFix) {
try {
// Note: There's a theoretical TOCTOU race between reading (line 137) and writing here,
// but it's benign: if another process fixed the file in between, we just overwrite
// valid JSON with `{}` which is also valid JSON. The write is idempotent.
writeFileSync(marketplacesPath, '{}', 'utf-8');
console.log('[main] Fixed known_marketplaces.json with empty object');
} catch (e) {
console.warn('[main] Failed to fix known_marketplaces.json:', e);
}
}
}

// Get icon path based on platform
function getIconPath(): string {
// In dev mode, __dirname is out/main, so we go up to project root then into resources
Expand All @@ -121,10 +186,10 @@ function getIconPath(): string {
: join(process.resourcesPath);

let iconName: string;
if (process.platform === 'darwin') {
if (isMacOS()) {
// Use PNG in dev mode (works better), ICNS in production
iconName = is.dev ? 'icon-256.png' : 'icon.icns';
} else if (process.platform === 'win32') {
} else if (isWindows()) {
iconName = 'icon.ico';
} else {
iconName = 'icon.png';
Expand Down Expand Up @@ -245,13 +310,13 @@ function createWindow(): void {

// Set app name before ready (for dock tooltip on macOS in dev mode)
app.setName('Auto Claude');
if (process.platform === 'darwin') {
if (isMacOS()) {
// Force the name to appear in dock on macOS
app.name = 'Auto Claude';
}

// Fix Windows GPU cache permission errors (0x5 Access Denied)
if (process.platform === 'win32') {
if (isWindows()) {
app.commandLine.appendSwitch('disable-gpu-shader-disk-cache');
app.commandLine.appendSwitch('disable-gpu-program-cache');
console.log('[main] Applied Windows GPU cache fixes');
Expand All @@ -263,18 +328,22 @@ app.whenReady().then(() => {
electronApp.setAppUserModelId('com.autoclaude.ui');

// Clear cache on Windows to prevent permission errors from stale cache
if (process.platform === 'win32') {
if (isWindows()) {
session.defaultSession.clearCache()
.then(() => console.log('[main] Cleared cache on startup'))
.catch((err) => console.warn('[main] Failed to clear cache:', err));
}

// Fix corrupted known_marketplaces.json on Windows before any Claude CLI calls
// This prevents crashes when the file is empty or contains [] instead of {}
fixKnownMarketplacesJson();

// Clean up stale update metadata from the old source updater system
// This prevents version display desync after electron-updater installs a new version
cleanupStaleUpdateMetadata();

// Set dock icon on macOS
if (process.platform === 'darwin') {
if (isMacOS()) {
const iconPath = getIconPath();
try {
const icon = nativeImage.createFromPath(iconPath);
Expand Down Expand Up @@ -458,7 +527,7 @@ app.whenReady().then(() => {

// Quit when all windows are closed (except on macOS)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
if (!isMacOS()) {
app.quit();
}
});
Expand Down
Loading