Skip to content

fix(session): fence turn-failure writes under the run lease; detect paused turns without the .paused marker (Eve2-F2, Eve2-F3) - #617

Merged
LinuxDevil merged 1 commit into
mainfrom
fix/eve2-f23-durability
Oct 12, 2026
Merged

LinuxDevil merged 1 commit into
mainfrom
fix/eve2-f23-durability

Conversation

@LinuxDevil

Copy link
Copy Markdown
Owner

What

Fixes two HIGH durability findings from the eve2 runtime-tools audit.

Eve2-F2 — recordFailure() poisoned a foreign checkpoint

AgentSession.recordFailure() did a raw load + save merging lastError { retryable: false } / attempts + 1 onto whatever checkpoint occupied <id>.turn-<n>, bypassing the run-lease fencing that saveStepCheckpoint/RunLease.save use. A run that stalled past its lease, then failed permanently, stamped its error on the checkpoint of the process that had taken the turn over — bricking that turn forever with LOUSHO_SESSION_TURN_FAILED.

Fix: the session now takes the turn's run lease itself before the run starts (holdRunLease), hands it to the run via the internal HELD_RUN_LEASE option (so the executor saves under it instead of taking its own), keeps it until record() is done, and writes the failure through RunLease.save — a fenced save that throws LOUSHO_SESSION_BUSY, writing nothing, once the lease is lost. Stores without lease support behave exactly as before.

Eve2-F3 — .paused marker gap

The <id>.paused pointer is written by markPaused() after the executor already saved the awaiting-approval checkpoint — two non-atomic durable writes. assertNotPaused consulted only the marker, so a agent.send(msg, { sessionId }) in the gap (or after a crash between the writes, permanently) ran a full turn beside the paused one.

Fix (option a, defense-in-depth): pausedSessionTurn now falls back to scanning <id>.turn-* checkpoints via CheckpointStore.list({ status: 'awaiting-approval' }) when the pointer is absent (or stale), returning the newest paused turn. Stores without list() keep the pointer-only behavior; a list() that is present but unavailable (KV binding without list) degrades gracefully.

Tests

  • src/session/sessionLease.test.ts: stalled session turn (file + SQLite stores) fails permanently after losing its lease — verifies the new holder's checkpoint keeps its own attempts: 1, lastError: { retryable: true } and stays resumable.
  • src/session/pausedSessionLock.test.ts: .paused marker deleted after a pause (the crash window) — agent.send(msg, { sessionId }), session.pending() and session.send() all still refuse with SessionAwaitingApprovalError.

Gates

  • npx tsc --noEmit -p tsconfig.json clean (tests tsconfig too)
  • npm run lint clean
  • npx vitest run src/session src/execution — 85 files / 1188 tests pass

No public API changes (HELD_RUN_LEASE / RunLeaseOptions already existed internally; no signature changes).

…nd treat an awaiting-approval turn checkpoint as paused (Eve2-F2, Eve2-F3)
@LinuxDevil
LinuxDevil merged commit 1dbcc56 into main Oct 12, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant