fix(external-agent): keep awaiting_review proposals reviewable when the run record is gone - #95
Conversation
…he run record is gone A persisted awaiting_review external edit session whose agent run record no longer exists (e.g. the editor/server restarted before the run was persisted) currently throws 'External proposal is active in another editor or no longer resumable' during hydration, orphaning the proposal: it can never be applied, rejected, or discarded. When the run record is genuinely gone (not merely claimed by another active editor), degrade gracefully instead of throwing: install the restored session without a run ledger, publish the awaiting_review proposal for review, and let apply/reject/discard proceed without requiring the missing run. When the run still exists and is owned elsewhere, the original safety guard is kept.
0xsline
left a comment
There was a problem hiding this comment.
Two blocking ownership cases remain:
-
external-bridge-hydration.ts:121-132treats a run missing fromloadAgentRuntimeSidecar()as definitively gone. That read useskvGet(), which falls back to IndexedDB after a project-store failure (sharedKv.ts:405-420). A stale/empty local cache can therefore report “gone” while the authoritative store still has a lease owned by another editor, allowing the run-less path to bypass ownership. -
Once installed without a ledger,
external-bridge-runtime.ts:234-299allows apply/reject without any claim.saveExternalProposal()is an unconditionalkvSet(externalProposalStore.ts:139-144), so two tabs can hydrate the same orphan and publish conflicting terminal states. Please add an authoritative, atomic proposal/run recovery claim (or equivalent CAS) and cover concurrent recovery plus offline-cache ambiguity. Also splithydrateStoredExternalBridge; it is now 52 lines, over the repo <50 function limit.
The submitted typecheck, changed-file lint, and runtime verify pass, but the new test covers only a single runtime and reject, so it does not exercise these races.
Problem
A persisted
awaiting_reviewexternal edit session whose agent run record no longer exists (e.g. the editor/server restarted before the run was persisted) currently fails hydration withExternal proposal is active in another editor or no longer resumable.This orphans the proposal permanently: it can never be applied, rejected, or discarded, and the project's external-edit surface is blocked (the UI also surfacesAgent run for edit session ... is unavailable).Root cause
hydrateStoredExternalBridgetreats two different situations identically whenExternalSessionRunLedger.resume()returnsnull:awaiting_reviewproposal is complete on disk and only needs the run ledger for durable artifact recording, not for review/apply/reject.Case 2 should degrade gracefully instead of throwing, but the current code throws for both.
Fix
external-bridge-hydration.ts: when the run cannot be resumed, check whether the run record actually still exists vialoadAgentRuntimeSidecar. Only throw when the record exists (owned elsewhere). When the record is gone, install the restored session withrun: nulland keep theawaiting_reviewproposal reviewable.external-bridge-runtime.ts:apply()andreject()no longer hard-require the run ledger (requireRun+confirmOwnership); they confirm ownership only when a run is present.discard()/markTerminalwere already null-safe.external-edit-session-runtime.verify.ts: new check that anawaiting_reviewproposal with a missing run record hydrates as reviewable and can be rejected.Verification
npx tsc -b --force✅npm run lint✅ (0 errors)npm run build(tsc -b && vite build) ✅npm test✅ (full suite)