From 6a6fbca9e07b677f39db89b9befe6fa147921cd9 Mon Sep 17 00:00:00 2001 From: Ramis Date: Sat, 27 Jun 2026 20:31:38 -0700 Subject: [PATCH] fix: stop agent crashing on Mongo errors during live loop A Mongo auth/connection failure in getGitStates escaped uncaught and killed the agent process on the first screen frame, so collision detection never ran. Make git-state fusion best-effort (degrade to vision-only) and wrap the whole onScreenFrame loop so no per-frame Gemini/GitHub/Mongo error can crash the long-running agent. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01AaCFWMkYQmTcuPsxaaACft --- backend/src/agent/podman.ts | 32 +++++++++++++++++++------------- backend/src/memory/db.ts | 22 ++++++++++++++-------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/backend/src/agent/podman.ts b/backend/src/agent/podman.ts index 97cf920..e4fec6d 100644 --- a/backend/src/agent/podman.ts +++ b/backend/src/agent/podman.ts @@ -43,21 +43,27 @@ export class PodMan { } async onScreenFrame(engineerId: string, jpeg: Buffer): Promise { - const ctx = await analyzeFrame(engineerId, this.podId, jpeg); - this.contexts.set(engineerId, ctx); - await recordObservation(ctx); + // Whole-frame guard: a Gemini/GitHub/Mongo failure on one frame must degrade + // gracefully, never crash the long-running live agent loop. + try { + const ctx = await analyzeFrame(engineerId, this.podId, jpeg); + this.contexts.set(engineerId, ctx); + await recordObservation(ctx); - // Fuse git ground truth: engineer_states written by scripts/podman-agent.mjs. - // Keyed by name (matches --name arg), same as LiveKit participant identity. - const gitStates = await getGitStates(this.podId); - for (const [id, c] of this.contexts) { - const git = gitStates.get(id); - if (git && git.changedFiles.length > 0) c.hasUnpushedChanges = true; + // Fuse git ground truth: engineer_states written by scripts/podman-agent.mjs. + // Keyed by name (matches --name arg), same as LiveKit participant identity. + const gitStates = await getGitStates(this.podId); + for (const [id, c] of this.contexts) { + const git = gitStates.get(id); + if (git && git.changedFiles.length > 0) c.hasUnpushedChanges = true; + } + + const github = await getGithubState(); // cached + const collisions = detectCollisions([...this.contexts.values()], github); + for (const collision of collisions) await this.handle(collision); + } catch (err) { + console.warn(`[agent] frame from ${engineerId} skipped: ${(err as Error).message}`); } - - const github = await getGithubState(); // cached - const collisions = detectCollisions([...this.contexts.values()], github); - for (const collision of collisions) await this.handle(collision); } private async handle(collision: Collision): Promise { diff --git a/backend/src/memory/db.ts b/backend/src/memory/db.ts index 5b64c73..19ad06e 100644 --- a/backend/src/memory/db.ts +++ b/backend/src/memory/db.ts @@ -69,15 +69,21 @@ export async function getGitStates(podId: string): Promise recentCommit?: string | null; gitUpdatedAt?: Date; }>('engineer_states'); - const docs = await col.find({ podId }).toArray(); const map = new Map(); - for (const doc of docs) { - map.set(doc.name, { - changedFiles: doc.changedFiles ?? [], - branch: doc.branch ?? null, - recentCommit: doc.recentCommit ?? null, - gitUpdatedAt: doc.gitUpdatedAt ?? null, - }); + try { + const docs = await col.find({ podId }).toArray(); + for (const doc of docs) { + map.set(doc.name, { + changedFiles: doc.changedFiles ?? [], + branch: doc.branch ?? null, + recentCommit: doc.recentCommit ?? null, + gitUpdatedAt: doc.gitUpdatedAt ?? null, + }); + } + } catch (err) { + // Best-effort: a Mongo hiccup degrades git fusion, it must not crash the + // live agent loop. Detection falls back to vision-only signals. + console.warn(`[memory] getGitStates failed: ${(err as Error).message}`); } return map; }