From 6afdb7798c8b2a98bc6b6b03054f564598028c11 Mon Sep 17 00:00:00 2001 From: BlackStar1453 Date: Wed, 25 Feb 2026 18:12:23 +0800 Subject: [PATCH 1/2] fix: disable console transport in production to prevent main thread hang In packaged Electron apps, console.log writes to a pipe/PTY whose buffer can fill up, causing the synchronous write() syscall to block the main thread indefinitely. This was observed as a 66-second UI freeze on 2026-02-25 (macOS hang report). Setting console transport level to false in production eliminates the blocking write path entirely. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/frontend/src/main/app-logger.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/frontend/src/main/app-logger.ts b/apps/frontend/src/main/app-logger.ts index dc984d417d..7d877d9380 100644 --- a/apps/frontend/src/main/app-logger.ts +++ b/apps/frontend/src/main/app-logger.ts @@ -35,8 +35,10 @@ log.transports.file.fileName = 'main.log'; // Note: We use electron-log's default archiveLogFn which properly rotates logs // by renaming old files to .old format. Custom implementations were problematic. -// Console transport - always show warnings and errors, debug only in dev mode -log.transports.console.level = process.env.NODE_ENV === 'development' ? 'debug' : 'warn'; +// Console transport - enable in dev mode only; in production (packaged app) console.log +// writes to a pipe/PTY whose buffer can fill up, blocking the main thread synchronously +// and causing UI hangs (see: hang report 2026-02-25, 66s freeze on write()). +log.transports.console.level = process.env.NODE_ENV === 'development' ? 'debug' : false; log.transports.console.format = '[{h}:{i}:{s}] [{level}] {text}'; // Guard console transport writes so broken stdio streams do not crash the app. { From e79693fe74d521e43fc3baafdd87ea71494c2433 Mon Sep 17 00:00:00 2001 From: BlackStar1453 Date: Wed, 25 Feb 2026 18:24:12 +0800 Subject: [PATCH 2/2] fix: include monorepo root dependencies in project_index.json ProjectAnalyzer._find_and_analyze_services() iterates subdirectories of service_locations but never analyzes the project root itself as a service. In monorepo projects where the root has package.json + node_modules (e.g., npm/pnpm workspaces), these root-level dependencies are completely missed, causing dependency_locations to be empty for the largest dependency directory (~1GB node_modules). This causes worktrees to either fail to create symlinks or have agents run npm install, duplicating the full node_modules per worktree. After the monorepo service discovery loop, check if the project root has dependency-bearing files (package.json, requirements.txt, pyproject.toml) and analyze it as a "root" service if it contributes dependency_locations. This ensures root-level node_modules gets included and properly symlinked into worktrees. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../analyzers/project_analyzer_module.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/apps/backend/analysis/analyzers/project_analyzer_module.py b/apps/backend/analysis/analyzers/project_analyzer_module.py index b7380dbb49..356a1abbdd 100644 --- a/apps/backend/analysis/analyzers/project_analyzer_module.py +++ b/apps/backend/analysis/analyzers/project_analyzer_module.py @@ -116,6 +116,24 @@ def _find_and_analyze_services(self) -> None: "language" ): # Only include if we detected something services[item.name] = service_info + + # Also check the project root itself for dependencies. + # Monorepo roots often have their own package.json + node_modules + # (e.g., npm/pnpm workspaces) that need to be shared with worktrees. + already_covered = self.project_dir in { + Path(s.get("path", "")) for s in services.values() + } + if not already_covered: + dep_indicators = ("package.json", "requirements.txt", "pyproject.toml") + root_has_deps = any( + (self.project_dir / f).exists() for f in dep_indicators + ) + if root_has_deps: + root_analyzer = ServiceAnalyzer(self.project_dir, "root") + root_info = root_analyzer.analyze() + root_deps = root_info.get("dependency_locations", []) + if root_deps: + services["root"] = root_info else: # Single project - analyze root analyzer = ServiceAnalyzer(self.project_dir, "main")