Skip to content

Polish lease UX: self-heal metadata damage, sanitize errors, retire lock sidecars / 打磨租约体验:元数据损坏自愈、错误脱敏、锁侧车清理#6030

Merged
SivanCola merged 2 commits into
esengine:main-v2from
SivanCola:fix/lease-experience-cleanup
Jul 5, 2026
Merged

Polish lease UX: self-heal metadata damage, sanitize errors, retire lock sidecars / 打磨租约体验:元数据损坏自愈、错误脱敏、锁侧车清理#6030
SivanCola merged 2 commits into
esengine:main-v2from
SivanCola:fix/lease-experience-cleanup

Conversation

@SivanCola

Copy link
Copy Markdown
Collaborator

Summary

Follow-up polish on the lease/CAS stack after the #6023 / #6025 hotfixes. Those two PRs fixed the deterministic root causes; this one closes the remaining ways a healthy single-window session could still present as busy, leak holder details into the UI, or litter the session directory — the secondary irritants reported across the same incident issues.

Stacked on #6023 (branched from it; merge #6023 first and this PR reduces to one commit).

Changes

1. Lease reclaim self-heals metadata damage

TryReclaimCurrentProcessSessionLease now arbitrates on the OS lock alone. A missing or unreadable lease.json (deleted by the user, quarantined by AV, torn by a crash) with a free lock is a leftover, not a holder. Previously an orphaned in-process registry entry whose info file disappeared could never be reclaimed — the fallback acquire re-hit the orphaned entry forever, wedging every rebuild as busy. The desktop gate (canReclaimCurrentProcessSessionLease) mirrors this: nil lease info now allows the reclaim attempt (the OS lock arbitrates), while a readable info naming a foreign runtime is still refused.

2. Raw lease errors can no longer reach the UI

The raw SessionLeaseError text carries the session path and the holder's host-pid-writer id (seen verbatim in #5990). Audited every surface end-to-end and sanitized the three that still leaked:

  • Startup bind failurestab.StartupErr feeds the topbar error banner and the polled tab metas; all three assignment sites now render the user-facing busy message.
  • ClearSession bridge returns (both the direct path and clearActiveSessionRuntime).
  • Recovery-lease callback — its error propagates through ctrl.Snapshot() into chat notices and bridge returns; the surfaced message is now sanitized while the raw details stay in slog.

The busy message also drops its dangling "before changing this setting" clause when no setting applies. Trash/delete paths, deferred rebuilds, and serve/ACP were audited and confirmed clean (serve/ACP never touch leases).

3. Lock sidecars retire on release instead of accumulating

SessionLease.Release now retires .lock / .lease.lock through the existing atomic take-then-remove helpers — non-blocking, and a no-op against any live holder or in-flight save, preserving the delete-atomic-with-release invariant. Previously the sidecars were only swept on the next boot reconcile, so ordinary use accumulated lock files (#6014 reported 5 of them alongside the recovery pile-up).

4. Autosave failure warnings stop streaming

A persistently failing disk (AV hold, full volume) used to emit a chat warning for every attempt of every turn (up to 3 per burst, unbounded across turns). Now: retries are slog-only, the user notice fires once when a burst gives up, and at most once per 5 minutes per tab. Explicit action saves keep immediate feedback; every failure is still logged.

5. Recovery branches tolerate index write failures

SaveRecoveryBranch no longer fails after the recovery transcript and meta are already durable just because the event-index (a listing accelerator) could not be written — matching the two save() call sites hardened in #6025.

6. Lease test suite feeds user-shape paths

The suite previously passed pre-canonicalized paths to the APIs under test — the exact pattern that let the Windows case-fold mismatch (#5999) escape it. A leaseTestPath helper now feeds mixed-case user-shape paths to every API call (identical off-Windows, exercises the fold on Windows), with internal-state setup keeping canonical keys. New regressions: orphan-without-info reclaim, corrupt-info reclaim, foreign-info refusal, sidecar retirement on release (including the racing-successor guard), StartupErr sanitization, and the autosave warning debounce.

Tests

  • go test ./internal/agent full (+ lease/canonical subset with -race) — pass.
  • cd desktop && go test . full suite — pass (one pre-existing assertion updated: it required the raw lease text in StartupErr, i.e. it had codified the leak; it now asserts the sanitized message and the absence of raw details).
  • GOOS=windows GOARCH=amd64 builds clean in both modules; gofmt clean.

Cache impact

Cache-impact: none. Desktop runtime ownership, error presentation, sidecar lifecycle, and tests only; no provider-visible prompts, tool schemas, request serialization, or compaction changes.

Refs #6014 #5999 #6027 (Phase-1 groundwork for the write-protection convergence proposal).

SivanCola added 2 commits July 5, 2026 22:17
The lease registry keys sessions through agent's canonical form (clean,
absolute, lowercased on Windows), while desktop sessionRuntimeKey kept the
raw path case. On Windows every tab path contains uppercase segments
(C:\Users\...), so the tab's own lease never matched its session key:
ensureSessionLease re-acquired against the process's own registry entry,
reclaim then hit the tab's live OS lock handle, and every model / effort /
token-mode / provider-key rebuild failed with the "already open in another
Reasonix window" error on a single window — including fresh installs.

- Export agent.CanonicalSessionPath and layer it into sessionRuntimeKey so
  desktop identity checks agree with the lease registry byte-for-byte.
- Normalize the canonicalTabSessionPath fallback (project-scope sessions)
  with clean+abs so shape variants cannot split one file into two keys.
- Keep case-folded keys out of SessionPath: detach/attach paths now carry
  the display path separately from the runtime key.
- Release only the build's own lease on startup-build failure, and skip
  claiming a lease the build merely reused (a concurrent switch owns it).
- Supersede in-flight startup builds when a model/effort/token switch
  installs its controller, so a slow blank-session build can no longer
  overwrite the new runtime or strip its lease.
- Regression tests: lease/runtime key agreement, idempotence, Windows case
  folding, project-scope fallback normalization, fast-path lease reuse.

Fixes esengine#5999. Fixes esengine#6006. Fixes esengine#5989. Fixes esengine#5990. Refs esengine#5996.
…rs, retire lock sidecars

Follow-up polish on the lease/CAS stack after the esengine#6023/esengine#6025 hotfixes,
closing the remaining ways a healthy single-window session could still
present as busy, leak holder details, or litter the session directory.

- Reclaim now arbitrates on the OS lock alone: a missing or unreadable
  lease.json (user cleanup, AV quarantine, crash tear) with a free lock is
  a leftover, not a holder. Previously an orphaned in-process entry whose
  info file disappeared could never be reclaimed - the fallback acquire
  re-hit the orphaned entry forever and every rebuild stayed busy. The
  desktop gate mirrors this: nil lease info allows the reclaim attempt,
  foreign holders are still refused.
- Raw lease errors (session path + host-pid-writer id) can no longer reach
  the UI: startup bind failures (StartupErr feeds the topbar banner and tab
  metas), ClearSession's bridge returns, and the recovery-lease callback
  (chat notices via snapshot errors) all render the user-facing busy
  message; raw details stay in slog. The busy message drops its dangling
  'before changing this setting' clause when no setting applies.
- SessionLease.Release retires the .lock/.lease.lock sidecars through the
  existing atomic take-then-remove helpers (non-blocking, no-op against any
  live holder or in-flight save), so ordinary use stops accumulating lock
  files that previously waited for the next boot reconcile (esengine#6014).
- Autosave failures warn once per burst and at most once per 5 minutes per
  tab: retries are slog-only, the user notice fires when the burst gives
  up. Explicit action saves keep immediate feedback. Every failure is still
  logged.
- SaveRecoveryBranch tolerates event-index write failures like the other
  save paths: the recovery transcript and meta are already durable, so a
  failed listing accelerator no longer discards a successful recovery.
- Lease test suite now feeds user-shape (mixed-case) paths to the APIs
  under test instead of pre-canonicalized ones - the pattern that let the
  Windows case-fold mismatch (esengine#5999) escape the suite - plus regressions
  for orphan-without-info, corrupt-info, foreign-info-refused, sidecar
  retirement on release, StartupErr sanitization, and the autosave warning
  debounce.

Refs esengine#6014 esengine#5999 esengine#6027.
@SivanCola SivanCola requested a review from esengine as a code owner July 5, 2026 16:00
@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 SivanCola merged commit 075b961 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