Skip to content

Fix session recovery persistence cleanup / 修复会话恢复持久化清理#5976

Merged
SivanCola merged 4 commits into
esengine:main-v2from
SivanCola:fix/session-recovery-event-log-cleanup
Jul 5, 2026
Merged

Fix session recovery persistence cleanup / 修复会话恢复持久化清理#5976
SivanCola merged 4 commits into
esengine:main-v2from
SivanCola:fix/session-recovery-event-log-cleanup

Conversation

@SivanCola

@SivanCola SivanCola commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Hide conflict-recovery copies from the normal topic list and stop showing the recovery badge/title as a regular conversation. Recovery keeps the original topic; copies stay reachable from History and are marked as conflict copies in the session tree and History drawer.
  • Add an append-only session event log with a compatibility checkpoint: normal saves append, while explicit rewrites remain versioned events.
  • Make the event log crash-safe and bounded: torn tails left by a crash or full disk are truncated by the next save instead of failing LoadSession; replace events and checkpoint rewrites are fsynced; past a size threshold the log compacts to a single replace event and the .jsonl checkpoint is refreshed, so direct readers and older binaries stay bounded-stale instead of frozen at first save.
  • Never touch files the native layer does not own: a legacy v0.x .events.jsonl transcript sitting at the native log path is read-ignored and preserved byte-for-byte; logs written by a newer schema fail loudly instead of being silently truncated.
  • Keep one-shot saves single-file: force saves (subagents, guardian, migrations, forks) never bootstrap an event log and fold an existing one into a single replace event.
  • Sweep the event log/index from every deletion surface through store.SessionSidecarFiles/clear, serve delete, ACP delete, subagent delete, and desktop trash — so deleted conversations can neither leak on disk nor be resurrected by LoadSession.
  • Move session discovery and artifact lifecycle onto the centralized session filename helpers; guardian sidecars are excluded from transcript discovery.
  • Clean up stale lease metadata, session lock sidecars, and orphan display entries during safe desktop cleanup paths. Desktop trash/delete now refuses to touch a session whose lease is held by another live process, preserving cross-process mutual exclusion.
  • Migrate checkpoint-only readers to event-aware APIs: prompt history (↑ recall), the serve session list, automatic topic titles, duplicate-trash comparison, history search recency, and doctor byte totals.
  • Count recovery-copy activity in topic summaries (recency, unread, time filters) without double-counting turns, and hide recovery-only topics only when they are unpinned with no live runtime session.

Context

This addresses the recovery-chain and stale artifact behavior discussed around #5940, #5947, and #5960. It does not claim to fix an unrelated process crash without a concrete crash log; it reduces the recovery cascade surface, makes the authoritative transcript tolerant of the crashes that used to trigger cascades, and prevents leftover lock/display/log sidecars from accumulating through the built-in cleanup and delete flows.

Verification

  • go test -p 1 ./...
  • go test . from desktop/
  • pnpm --dir desktop/frontend typecheck
  • pnpm --dir desktop/frontend check:css
  • git diff --check
  • New regression coverage: torn-tail self-heal, foreign/legacy log read-ignore (boot migration end-to-end), future-schema rejection, broken append chains, concurrent load-during-append, compaction growth bound, force-save single-file semantics, deletion sweeps on all five surfaces asserting no resurrection via LoadSession, external-lease trash guard, event-aware prompt history / serve list / auto titles, duplicate-trash content comparison, and the recovery-only topic hiding matrix.

@github-actions github-actions Bot added v2 Go rewrite (1.x) — main-v2 branch, active development desktop Wails desktop app (desktop/**) agent Core agent loop (internal/agent, internal/control) labels Jul 5, 2026
SivanCola added 3 commits July 5, 2026 13:34
- Replay the event log tolerantly: torn tails are truncated by the next
  save instead of bricking LoadSession; foreign/legacy .events.jsonl files
  squatting the native log path are read-ignored and never written; logs
  from a newer schema fail loudly instead of being silently truncated.
- Bound log growth: past a size threshold the log compacts to a single
  replace event, and every rewrite/compaction refreshes the .jsonl
  checkpoint so direct readers and older binaries stay bounded-stale.
- Keep force saves (subagents, guardian, migrations, forks) single-file:
  they never bootstrap an event log and fold an existing one instead.
- Sweep the event log/index from every deletion surface through
  store.SessionSidecarFiles — /clear, serve delete, ACP delete, subagent
  delete, desktop trash — so deleted conversations cannot leak or be
  resurrected; guardian sidecars are excluded from transcript discovery.
- Guard desktop trash/delete with an external-lease busy check so removing
  lease files cannot break cross-process mutual exclusion.
- Migrate frozen-checkpoint readers to event-aware APIs: prompt history,
  serve session list, auto topic titles, duplicate-trash comparison,
  history search recency, and doctor byte totals.
- Count recovery-copy activity in topic summaries, hide recovery-only
  topics only when unpinned with no live runtime session, and mark
  conflict copies in the session tree and History drawer.
Review follow-ups on the event-log hardening:

- Close the probe-then-delete lease window: desktop trash, reconcile, and
  permanent delete now take a SessionRemovalGuard that holds the session's
  save lock and lease lock across the whole operation (built on esengine#5977's
  delete-while-holding-flock primitive), so another process can no longer
  acquire the lease between a busy probe and the sidecar removal and then
  lose its freshly locked lease file. Live owners surface as a sanitized
  busy error; the lock files are deleted atomically with the release.
- Run overlong-named sessions (pre-bounded recovery cascade names) in
  checkpoint-only mode: their event log/index sidecars would exceed the
  filesystem name limit, so the probe treats them as non-native instead of
  failing saves with ENAMETOOLONG.
- Fix staticcheck QF1003: tagged switch for the save-reason mapping.
@SivanCola

Copy link
Copy Markdown
Collaborator Author

Review follow-ups addressed in 7bb7290 (after merging main-v2 with #5977):

Lease split-window on delete/trash paths (P1). The one-shot busy probe is gone. Desktop trash, trash-reconcile, and permanent delete now acquire an agent.SessionRemovalGuard that non-blockingly takes both the session's save lock and lease lock before any artifact is moved, holds them across the whole operation, and deletes the lock sidecars atomically with the release — built directly on #5977's delete-while-holding-flock primitive (tryTakeSessionLockFile / RemoveAndUnlock, Unix unlink-under-flock, Windows delete-disposition). A live holder of either lock (another process, or another runtime in this process) surfaces as a sanitized "session is in use" error instead of a deletion. Regression tests hold real locks: the guard blocks lease acquisition while held, all three desktop entry points refuse while a lease is live and leave every file untouched, and the sidecars are gone after a successful trash.

Interaction found while merging #5977. Its overlong-filename test caught that the event-index sidecar (<stem>.event-index.json) exceeds NAME_MAX for pre-bounded recovery-cascade names, failing saves with ENAMETOOLONG. Overlong-named sessions now run checkpoint-only (no event log/index) until reconcileOverlongSessionFilenames renames them.

Lint (P2). QF1003 fixed (tagged switch on the save mode). golangci-lint run is clean on both the root and desktop modules locally with the CI version.

#5968. No dependency from this PR; it touches the same desktop files and will need a rebase after whichever lands first.

Verification on 7bb7290: go test -p 1 ./... (all packages), go test . in desktop/, golangci-lint run on both modules, pnpm --dir desktop/frontend typecheck && check:css, git diff --check.

@SivanCola SivanCola marked this pull request as ready for review July 5, 2026 06:24
@SivanCola SivanCola requested a review from esengine as a code owner July 5, 2026 06:24
@SivanCola SivanCola merged commit f4ef59f into esengine:main-v2 Jul 5, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Core agent loop (internal/agent, internal/control) desktop Wails desktop app (desktop/**) v2 Go rewrite (1.x) — main-v2 branch, active development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant