fix(bridge): 4 race condition fixes for approval lifecycle

This commit is contained in:
2026-03-15 10:43:43 +09:00
parent c910c7c386
commit f96203646e
5 changed files with 55 additions and 1 deletions

View File

@@ -481,6 +481,24 @@ async function setupApprovalObserver() {
requiredScript: 'jetskiAgent.js', // JS entry point
},
];
// ── FIX #1: File lock to prevent multi-instance HTML patching race ──
const lockFile = path.join(scriptDir, '.patch-lock');
let lockAcquired = false;
try {
if (fs.existsSync(lockFile)) {
const lockAge = Date.now() - fs.statSync(lockFile).mtimeMs;
if (lockAge < 30_000) {
logToFile(`[OBSERVER] another instance is patching (lock age=${Math.round(lockAge/1000)}s) — skipping`);
return; // Exit setupApprovalObserver entirely
}
logToFile(`[OBSERVER] stale lock (age=${Math.round(lockAge/1000)}s) — force-acquiring`);
}
fs.writeFileSync(lockFile, JSON.stringify({ pid: process.pid, ts: Date.now() }), 'utf-8');
lockAcquired = true;
} catch (lockErr: any) {
logToFile(`[OBSERVER] lock acquire error: ${lockErr.message} — proceeding anyway`);
}
for (const spec of htmlFileSpecs) {
const htmlPath = path.join(scriptDir, spec.name);
const backupPath = htmlPath + '.orig';
@@ -584,6 +602,12 @@ async function setupApprovalObserver() {
logToFile(`[OBSERVER] ${spec.name} patch error: ${e.message}`);
}
}
// Release patch lock
if (lockAcquired) {
try { fs.unlinkSync(lockFile); } catch { }
logToFile('[OBSERVER] patch lock released');
}
}
// 4. Update product.json checksums so vscode-file:// serves our patched files
@@ -1918,6 +1942,8 @@ function setupMonitor() {
pd.status = 'auto_resolved';
fs.writeFileSync(pfPath, JSON.stringify(pd, null, 2), 'utf-8');
logToFile(`[AUTO-RESOLVE] step=${lastPendingStepIndex} progressed → marked ${pf} (age=${Math.round(ageMs/1000)}s)`);
// FIX #3: Notify Discord that user approved locally
writeChatSnapshot(`✅ **AG에서 직접 승인됨** (step ${lastPendingStepIndex})\n\n\`${(pd.command || '').substring(0, 200)}\``);
}
}
} catch (e: any) { logToFile(`[AUTO-RESOLVE] error: ${e.message}`); }
@@ -2452,6 +2478,14 @@ async function processResponseFile(filePath: string) {
if (fs.existsSync(pendingFile)) {
try {
const pending = JSON.parse(fs.readFileSync(pendingFile, 'utf-8'));
// FIX #2: Skip if pending was already resolved locally (auto_resolve or expired)
if (pending.status === 'auto_resolved' || pending.status === 'expired') {
logToFile(`[RESPONSE] SKIP — pending already ${pending.status} (rid=${resp.request_id})`);
try { fs.unlinkSync(filePath); } catch { }
return;
}
sessionId = pending.conversation_id || '';
isDomObserver = pending.auto_detected === true
|| pending.source === 'dom_observer';