From 923880f5b2eba601a412b437993b3e586dfb99af Mon Sep 17 00:00:00 2001 From: Andy <119136210+AndyMik90@users.noreply.github.com> Date: Tue, 10 Feb 2026 13:29:27 +0100 Subject: [PATCH] fix(title-generator): add production path resolution for backend source (#1778) * fix(title-generator): add production path resolution for backend source getAutoBuildSourcePath() only checked development-mode relative paths, causing AI title generation to silently fail in packaged builds. Added app.isPackaged check with userData override and process.resourcesPath fallbacks, matching the pattern already used by terminal-name-generator and other services. Co-Authored-By: Claude Opus 4.6 * refactor(path-resolution): consolidate duplicated backend path logic into shared resolver Both title-generator and terminal-name-generator duplicated the production path resolution logic (userData override, resourcesPath fallback, dev paths) that already exists in getEffectiveSourcePath() from updater/path-resolver.ts. Replaced inline implementations with the shared utility, matching the pattern used by insights/config.ts. Also removed unused imports (app, fileURLToPath, __dirname) that were only needed for the old inline path resolution. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- .../src/main/terminal-name-generator.ts | 42 ++++--------------- apps/frontend/src/main/title-generator.ts | 27 +++++------- 2 files changed, 19 insertions(+), 50 deletions(-) diff --git a/apps/frontend/src/main/terminal-name-generator.ts b/apps/frontend/src/main/terminal-name-generator.ts index 39337be6..6c1e3e9b 100644 --- a/apps/frontend/src/main/terminal-name-generator.ts +++ b/apps/frontend/src/main/terminal-name-generator.ts @@ -1,16 +1,11 @@ import path from 'path'; -import { fileURLToPath } from 'url'; import { existsSync, readFileSync } from 'fs'; import { spawn } from 'child_process'; -import { app } from 'electron'; - -// ESM-compatible __dirname -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); import { EventEmitter } from 'events'; import { detectRateLimit, createSDKRateLimitInfo, getBestAvailableProfileEnv } from './rate-limit-detector'; import { parsePythonCommand } from './python-detector'; import { pythonEnvManager } from './python-env-manager'; +import { getEffectiveSourcePath } from './updater/path-resolver'; /** * Debug logging - only logs when DEBUG=true or in development mode @@ -51,35 +46,16 @@ export class TerminalNameGenerator extends EventEmitter { return this.autoBuildSourcePath; } - // In packaged app, check userData override first (consistent with path-resolver.ts) - if (app.isPackaged) { - // Check for user-updated backend source first (takes priority over bundled) - const overridePath = path.join(app.getPath('userData'), 'backend-source'); - if (existsSync(overridePath) && existsSync(path.join(overridePath, 'runners', 'spec_runner.py'))) { - debug('Using user-updated backend from userData:', overridePath); - return overridePath; - } - // Fall back to bundled backend in resources - const resourcesPath = path.join(process.resourcesPath, 'backend'); - if (existsSync(resourcesPath) && existsSync(path.join(resourcesPath, 'runners', 'spec_runner.py'))) { - debug('Using bundled backend from resources:', resourcesPath); - return resourcesPath; - } + // Use shared path resolver which handles: + // 1. User settings (autoBuildPath) + // 2. userData override (backend-source) for user-updated backend + // 3. Bundled backend (process.resourcesPath/backend) + // 4. Development paths + const effectivePath = getEffectiveSourcePath(); + if (existsSync(effectivePath) && existsSync(path.join(effectivePath, 'runners', 'spec_runner.py'))) { + return effectivePath; } - // Development mode paths - const possiblePaths = [ - // Apps structure: from out/main -> apps/backend - path.resolve(__dirname, '..', '..', '..', 'backend'), - path.resolve(app.getAppPath(), '..', 'backend'), - path.resolve(process.cwd(), 'apps', 'backend') - ]; - - for (const p of possiblePaths) { - if (existsSync(p) && existsSync(path.join(p, 'runners', 'spec_runner.py'))) { - return p; - } - } return null; } diff --git a/apps/frontend/src/main/title-generator.ts b/apps/frontend/src/main/title-generator.ts index 29545cdc..f2225674 100644 --- a/apps/frontend/src/main/title-generator.ts +++ b/apps/frontend/src/main/title-generator.ts @@ -1,18 +1,13 @@ import path from 'path'; -import { fileURLToPath } from 'url'; import { existsSync, readFileSync } from 'fs'; import { spawn } from 'child_process'; -import { app } from 'electron'; - -// ESM-compatible __dirname -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); import { EventEmitter } from 'events'; import { detectRateLimit, createSDKRateLimitInfo, getBestAvailableProfileEnv } from './rate-limit-detector'; import { parsePythonCommand, getValidatedPythonPath } from './python-detector'; import { getConfiguredPythonPath } from './python-env-manager'; import { getAPIProfileEnv } from './services/profile'; import { getOAuthModeClearVars } from './agent/env-utils'; +import { getEffectiveSourcePath } from './updater/path-resolver'; /** * Debug logging - only logs when DEBUG=true or in development mode @@ -67,18 +62,16 @@ export class TitleGenerator extends EventEmitter { return this.autoBuildSourcePath; } - const possiblePaths = [ - // Apps structure: from out/main -> apps/backend - path.resolve(__dirname, '..', '..', '..', 'backend'), - path.resolve(app.getAppPath(), '..', 'backend'), - path.resolve(process.cwd(), 'apps', 'backend') - ]; - - for (const p of possiblePaths) { - if (existsSync(p) && existsSync(path.join(p, 'runners', 'spec_runner.py'))) { - return p; - } + // Use shared path resolver which handles: + // 1. User settings (autoBuildPath) + // 2. userData override (backend-source) for user-updated backend + // 3. Bundled backend (process.resourcesPath/backend) + // 4. Development paths + const effectivePath = getEffectiveSourcePath(); + if (existsSync(effectivePath) && existsSync(path.join(effectivePath, 'runners', 'spec_runner.py'))) { + return effectivePath; } + return null; }