fix(offload): increase entrypoint verbosity for easier debugging

This commit is contained in:
mkorwel
2026-03-17 21:56:25 -07:00
parent 51f1ebecbd
commit ae387866c5
4 changed files with 62 additions and 40 deletions
+23 -15
View File
@@ -1,12 +1,13 @@
/** /**
* Offload Attach Utility (Local) * Offload Attach Utility (Local)
* *
* Re-attaches to a running tmux session on the worker. * Re-attaches to a running tmux session inside the container on the worker.
*/ */
import { spawnSync } from 'child_process';
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import { spawnSync } from 'child_process';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { ProviderFactory } from './providers/ProviderFactory.ts';
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(__dirname, '../../../..'); const REPO_ROOT = path.resolve(__dirname, '../../../..');
@@ -23,23 +24,28 @@ export async function runAttach(args: string[], env: NodeJS.ProcessEnv = process
return 1; return 1;
} }
// ... (load settings) const settingsPath = path.join(REPO_ROOT, '.gemini/offload/settings.json');
const settingsPath = path.join(REPO_ROOT, '.gemini/settings.json'); if (!fs.existsSync(settingsPath)) {
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
const config = settings.maintainer?.deepReview;
if (!config) {
console.error('❌ Settings not found. Run "npm run offload:setup" first.'); console.error('❌ Settings not found. Run "npm run offload:setup" first.');
return 1; return 1;
} }
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
const config = settings.deepReview;
if (!config) {
console.error('❌ Deep Review configuration not found.');
return 1;
}
const { projectId, zone } = config;
const targetVM = `gcli-offload-${env.USER || 'mattkorwel'}`;
const provider = ProviderFactory.getProvider({ projectId, zone, instanceName: targetVM });
const { remoteHost } = config;
const sshConfigPath = path.join(REPO_ROOT, '.gemini/offload_ssh_config');
const sessionName = `offload-${prNumber}-${action}`; const sessionName = `offload-${prNumber}-${action}`;
const finalSSH = `ssh -F ${sshConfigPath} -t ${remoteHost} "tmux attach-session -t ${sessionName}"`; const containerAttach = `sudo docker exec -it maintainer-worker sh -c ${q(`tmux attach-session -t ${sessionName}`)}`;
const finalSSH = provider.getRunCommand(containerAttach, { interactive: true });
console.log(`🔗 Attaching to session: ${sessionName}...`); console.log(`🔗 Attaching to session: ${sessionName}...`);
// 2. Open in iTerm2 if within Gemini AND NOT --local
const isWithinGemini = !!env.GEMINI_CLI || !!env.GEMINI_SESSION_ID || !!env.GCLI_SESSION_ID; const isWithinGemini = !!env.GEMINI_CLI || !!env.GEMINI_SESSION_ID || !!env.GCLI_SESSION_ID;
if (isWithinGemini && !isLocal) { if (isWithinGemini && !isLocal) {
const tempCmdPath = path.join(process.env.TMPDIR || '/tmp', `offload-attach-${prNumber}.sh`); const tempCmdPath = path.join(process.env.TMPDIR || '/tmp', `offload-attach-${prNumber}.sh`);
@@ -48,16 +54,18 @@ export async function runAttach(args: string[], env: NodeJS.ProcessEnv = process
const appleScript = ` const appleScript = `
on run argv on run argv
tell application "iTerm" tell application "iTerm"
set newWindow to (create window with default profile) tell current window
tell current session of newWindow set newTab to (create tab with default profile)
write text (item 1 of argv) & return tell current session of newTab
write text (item 1 of argv) & return
end tell
end tell end tell
activate activate
end tell end tell
end run end run
`; `;
spawnSync('osascript', ['-', tempCmdPath], { input: appleScript }); spawnSync('osascript', ['-', tempCmdPath], { input: appleScript });
console.log(`✅ iTerm2 window opened for ${sessionName}.`); console.log(`✅ iTerm2 tab opened for ${sessionName}.`);
return 0; return 0;
} }
+19 -19
View File
@@ -2,12 +2,13 @@
* Universal Offload Cleanup (Local) * Universal Offload Cleanup (Local)
* *
* Surgical or full cleanup of sessions and worktrees on the GCE worker. * Surgical or full cleanup of sessions and worktrees on the GCE worker.
* Refactored to use WorkerProvider for container compatibility.
*/ */
import { spawnSync } from 'child_process';
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import readline from 'readline'; import readline from 'readline';
import { ProviderFactory } from './providers/ProviderFactory.ts';
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(__dirname, '../../../..'); const REPO_ROOT = path.resolve(__dirname, '../../../..');
@@ -22,53 +23,52 @@ async function confirm(question: string): Promise<boolean> {
}); });
} }
export async function runCleanup(args: string[]) { export async function runCleanup(args: string[], env: NodeJS.ProcessEnv = process.env) {
const prNumber = args[0]; const prNumber = args[0];
const action = args[1]; const action = args[1];
const settingsPath = path.join(REPO_ROOT, '.gemini/settings.json'); const settingsPath = path.join(REPO_ROOT, '.gemini/offload/settings.json');
if (!fs.existsSync(settingsPath)) { if (!fs.existsSync(settingsPath)) {
console.error('❌ Settings not found. Run "npm run offload:setup" first.'); console.error('❌ Settings not found. Run "npm run offload:setup" first.');
return 1; return 1;
} }
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
const config = settings.maintainer?.deepReview; const config = settings.deepReview;
if (!config) { if (!config) {
console.error('❌ Offload configuration not found.'); console.error('❌ Offload configuration not found.');
return 1; return 1;
} }
const { remoteHost } = config; const { projectId, zone } = config;
const targetVM = `gcli-offload-${env.USER || 'mattkorwel'}`;
const provider = ProviderFactory.getProvider({ projectId, zone, instanceName: targetVM });
if (prNumber && action) { if (prNumber && action) {
const sessionName = `offload-${prNumber}-${action}`; const sessionName = `offload-${prNumber}-${action}`;
const worktreePath = `~/dev/worktrees/${sessionName}`; const worktreePath = `/home/node/dev/worktrees/${sessionName}`;
console.log(`🧹 Surgically removing session and worktree for ${prNumber}-${action}...`); console.log(`🧹 Surgically removing session and worktree for ${prNumber}-${action}...`);
// Kill specific tmux session // Kill specific tmux session inside container
spawnSync(`ssh ${remoteHost} "tmux kill-session -t ${sessionName} 2>/dev/null"`, { shell: true }); await provider.exec(`tmux kill-session -t ${sessionName} 2>/dev/null`, { wrapContainer: 'maintainer-worker' });
// Remove specific worktree // Remove specific worktree inside container
spawnSync(`ssh ${remoteHost} "cd ~/dev/main && git worktree remove -f ${worktreePath} 2>/dev/null"`, { shell: true }); await provider.exec(`cd /home/node/dev/main && git worktree remove -f ${worktreePath} 2>/dev/null && git worktree prune`, { wrapContainer: 'maintainer-worker' });
spawnSync(`ssh ${remoteHost} "cd ~/dev/main && git worktree prune"`, { shell: true });
console.log(`✅ Cleaned up ${prNumber}-${action}.`); console.log(`✅ Cleaned up ${prNumber}-${action}.`);
return 0; return 0;
} }
// --- Bulk Cleanup (Old Behavior) --- // --- Bulk Cleanup ---
console.log(`🧹 Starting BULK cleanup on ${remoteHost}...`); console.log(`🧹 Starting BULK cleanup on ${targetVM}...`);
// 1. Standard Cleanup // 1. Standard Cleanup
console.log(' - Killing ALL remote tmux sessions...'); console.log(' - Killing ALL remote tmux sessions...');
spawnSync(`ssh ${remoteHost} "tmux kill-server"`, { shell: true }); await provider.exec(`tmux kill-server`, { wrapContainer: 'maintainer-worker' });
console.log(' - Cleaning up ALL Git Worktrees...'); console.log(' - Cleaning up ALL Git Worktrees...');
spawnSync(`ssh ${remoteHost} "cd ~/dev/main && git worktree prune"`, { shell: true }); await provider.exec(`cd /home/node/dev/main && git worktree prune && rm -rf /home/node/dev/worktrees/*`, { wrapContainer: 'maintainer-worker' });
spawnSync(`ssh ${remoteHost} "rm -rf ~/dev/worktrees/*"`, { shell: true });
console.log('✅ Remote environment cleared.'); console.log('✅ Remote environment cleared.');
@@ -76,8 +76,8 @@ export async function runCleanup(args: string[]) {
const shouldWipe = await confirm('\nWould you like to COMPLETELY wipe the remote workspace (main clone)?'); const shouldWipe = await confirm('\nWould you like to COMPLETELY wipe the remote workspace (main clone)?');
if (shouldWipe) { if (shouldWipe) {
console.log(`🔥 Wiping ~/dev/main...`); console.log(`🔥 Wiping /home/node/dev/main...`);
spawnSync(`ssh ${remoteHost} "rm -rf ~/dev/main && mkdir -p ~/dev/main"`, { stdio: 'inherit', shell: true }); await provider.exec(`rm -rf /home/node/dev/main && mkdir -p /home/node/dev/main`, { wrapContainer: 'maintainer-worker' });
console.log('✅ Remote hub wiped. You will need to run npm run offload:setup again.'); console.log('✅ Remote hub wiped. You will need to run npm run offload:setup again.');
} }
return 0; return 0;
+15 -5
View File
@@ -24,19 +24,29 @@ async function main() {
const workDir = process.cwd(); // This is remoteWorkDir as set in review.ts const workDir = process.cwd(); // This is remoteWorkDir as set in review.ts
const targetDir = path.join(workDir, branchName); const targetDir = path.join(workDir, branchName);
// Path to the locally installed binaries in the work directory // Use global tsx or fallback to local
const tsxBin = path.join(workDir, 'node_modules/.bin/tsx'); const tsxBin = 'tsx';
const geminiBin = path.join(workDir, 'node_modules/.bin/gemini'); // Gemini bin will be needed by the interactive session later
// We'll try to find it in the main repo or current worktree
const geminiBin = fs.existsSync(path.join(workDir, 'node_modules/.bin/gemini'))
? path.join(workDir, 'node_modules/.bin/gemini')
: path.join(path.dirname(workDir), 'main/node_modules/.bin/gemini');
const action = process.argv[5] || 'review';
// 1. Run the Parallel Reviewer // 1. Run the Parallel Reviewer
console.log('🚀 Launching Parallel Review Worker...'); console.log('🚀 Launching Parallel Review Worker...');
const workerResult = spawnSync(tsxBin, [path.join(__dirname, 'worker.ts'), prNumber, branchName, policyPath], { console.log(` - Script: ${path.join(__dirname, 'worker.ts')}`);
console.log(` - Action: ${action}`);
const workerResult = spawnSync(tsxBin, [path.join(__dirname, 'worker.ts'), prNumber, branchName, policyPath, action], {
stdio: 'inherit', stdio: 'inherit',
env: { ...process.env, GEMINI_CLI_HOME: ISOLATED_CONFIG } env: { ...process.env, GEMINI_CLI_HOME: ISOLATED_CONFIG }
}); });
if (workerResult.status !== 0) { if (workerResult.status !== 0) {
console.error('❌ Worker failed. Check the logs above.'); console.error(`❌ Worker failed with exit code ${workerResult.status}.`);
if (workerResult.error) console.error(' Error:', workerResult.error.message);
} }
// 2. Launch the Interactive Gemini Session (Local Nightly) // 2. Launch the Interactive Gemini Session (Local Nightly)
+5 -1
View File
@@ -39,7 +39,11 @@ export async function runWorker(args: string[]) {
process.chdir(targetDir); process.chdir(targetDir);
} }
const geminiBin = path.join(workDir, 'node_modules/.bin/gemini'); // Find gemini binary (try worktree, then fallback to main repo)
let geminiBin = path.join(targetDir, 'node_modules/.bin/gemini');
if (!fs.existsSync(geminiBin)) {
geminiBin = path.join(path.dirname(targetDir), 'main/node_modules/.bin/gemini');
}
// 2. Dispatch to Playbook // 2. Dispatch to Playbook
switch (action) { switch (action) {