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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Andy
2026-02-10 13:29:27 +01:00
committed by GitHub
parent 390ba6a588
commit 923880f5b2
2 changed files with 19 additions and 50 deletions
@@ -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;
}
+10 -17
View File
@@ -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;
}