Skip to content

fix(files): fix savingRef mutex integrity race after discard correction#5554

Merged
waleedlatif1 merged 4 commits into
stagingfrom
files-autosave-flush-fixups
Jul 10, 2026
Merged

fix(files): fix savingRef mutex integrity race after discard correction#5554
waleedlatif1 merged 4 commits into
stagingfrom
files-autosave-flush-fixups

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Follow-up to improvement(files): remove Save UI in favor of silent autosave with local-first draft recovery #5549 (merged) — 3 fixes surfaced by an independent 4-agent audit after merge
  • save()'s deferred MIN_SAVING_DISPLAY_MS status timer could clobber savingRef/trigger a concurrent resave even after discard()'s correction had claimed the save slot — now guarded on inFlightRef.current
  • Render-time discard un-suppress check was keyed off isDirty, which a stale save's markSavedContent landing post-discard could transiently corrupt — now keyed off a discardTargetRef captured at discard time
  • Hardened local-draft recovery to key its once-per-mount guard on the specific draft key instead of a bare boolean (defense in depth, no behavior change for current callers)

Type of Change

  • Bug fix

Testing

  • New regression test: does not let the original save leftover status timer clobber a still-running correction — verified it fails pre-fix (3 concurrent saves) and passes post-fix
  • bun run type-check, bunx biome check, and the full autosave/files/knowledge vitest suite (509 tests) all pass

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

…press to captured target

An independent 4-agent audit (beyond the bot review loop) converged on a real
race in the round-11 discard-correction fix:

- save()'s deferred MIN_SAVING_DISPLAY_MS status timer resolves as a macrotask
  well after the save's own promise (used to sequence discard's correction)
  has already settled as a microtask. That timer unconditionally reset
  savingRef/triggered a trailing resave, even after discard's correction had
  since claimed the save slot for its own in-flight write — letting a fresh
  debounced save start concurrently with the correction. Now guarded with
  `if (inFlightRef.current) return` before touching savingRef, so it only
  acts when nothing else has claimed the slot since.

- The render-time un-suppress check keyed off isDirty, which a stale save's
  markSavedContent(next) landing after discard can transiently corrupt
  (overwriting savedContent with the pre-discard value while content has
  already been reverted), causing a spurious "genuinely new edit" signal.
  Now keyed off a discardTargetRef captured at discard time instead, which
  that corruption doesn't touch.

Also hardened recovery to key its once-per-mount guard on the specific
draft key rather than a bare boolean, so a hypothetical future caller that
reuses a hook instance across files would still get correct recovery
(today's real callers already remount per file, so this is defense in
depth, not a behavior change for any current caller).
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 10, 2026 1:55am

Request Review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes affect file autosave and server sync around discard, with race-sensitive ref/timer logic; risk is mitigated by focused hook changes and many new tests, but incorrect behavior could still lose or mis-save edits.

Overview
Follow-up hardening for useAutosave when the user discards while a network save is still in flight. Discard now tracks a discardTargetRef (baseline at discard time) and resets suppression on draftKey changes so another file or an enabled streaming toggle is not treated like a document switch.

Corrective server writes move into runCorrection, which owns savingRef / inFlightRef, sets terminal saveStatus (idle or error), invokes onDiscardCorrectionFailed, and re-schedules autosave for edits typed while the correction held the mutex. saveImmediately can retry a failed correction even when content already matches savedContent (dirty-check would no-op).

A completed save’s MIN_SAVING_DISPLAY_MS timer no longer updates status or releases the mutex while discard is active or another save is in flight. Local-draft recovery’s one-shot guard is keyed by effectiveDraftKey instead of a mount-wide boolean.

Adds a broad regression suite in use-autosave.test.tsx for timer races, status stuck on saving, correction failures, file switches, superseding edits, and retry via saveImmediately.

Reviewed by Cursor Bugbot for commit 76964d4. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit b0f9bf9. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR tightens autosave behavior around discard correction. The main changes are:

  • Guards the original save status timer while a correction owns the save slot.
  • Tracks the discard target separately from dirty state.
  • Adds retry handling for failed discard corrections.
  • Keys draft recovery to the active draft key.
  • Adds regression tests for discard correction races and status outcomes.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
apps/sim/hooks/use-autosave.ts Updates discard correction ownership, retry state, draft-key reset handling, and save chaining after correction settlement.
apps/sim/hooks/use-autosave.test.tsx Adds tests for correction timers, failed corrections, retry behavior, key switching, enabled toggles, and edits made while correction is running.

Reviews (5): Last reviewed commit: "fix(files): key discard state to raw dra..." | Re-trigger Greptile

Comment thread apps/sim/hooks/use-autosave.ts Outdated
@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR tightens autosave discard handling and draft recovery. The main changes are:

  • Guarding the delayed save-status timer while a correction save owns the in-flight slot.
  • Tracking the discard target separately from live savedContent.
  • Keying local-draft recovery by draft key instead of one mount-wide boolean.
  • Adding a regression test for the stale timer and correction-save race.

Confidence Score: 5/5

The changed flow looks mergeable after small cleanup around discard-state edge cases.

  • The mutex handoff fix is covered by the new test.
  • A failed save after discard can skip the visible error state.
  • Discard suppression can carry across a draft-key change when the new content matches the old discard target.

apps/sim/hooks/use-autosave.ts

Important Files Changed

Filename Overview
apps/sim/hooks/use-autosave.ts Updates discard synchronization, status timer behavior, and draft recovery keying; two edge cases can still leave callers with stale or hidden autosave state.
apps/sim/hooks/use-autosave.test.tsx Adds coverage for the stale display timer racing with a still-running discard correction save.

Reviews (2): Last reviewed commit: "fix(files): fix savingRef mutex integrit..." | Re-trigger Greptile

Comment thread apps/sim/hooks/use-autosave.ts Outdated
Comment thread apps/sim/hooks/use-autosave.ts Outdated
…ion leak

Greptile round-1 review on the follow-up fix PR caught 3 real gaps in the
discard/correction flow:

- The display timer's !discardedRef guard (added to stop a stale save's
  status update from clobbering a running correction) also silently
  suppressed the idle-timer reschedule, and discard()'s own correction never
  set a terminal status on settle — so saveStatus could stick on 'saving'
  forever after a successful correction, and a failed correction surfaced
  only via the onDiscardCorrectionFailed callback with no status change.
  Now the correction's own .then()/.catch() sets 'idle'/'error' once it
  owns the flow (only when nothing has since un-suppressed discard).

- The discard-suppression un-suppress check compared content against the
  captured discard target, but never reset if a hook instance were reused
  across draftKeys — a coincidental content match with the previous file's
  target would keep discard permanently suppressing saves for the new file.
  Reset discardedRef whenever the effective draftKey changes.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/hooks/use-autosave.ts
Comment thread apps/sim/hooks/use-autosave.ts
Cursor Bugbot found the discard correction's finally() cleared the save
mutex but never rechecked for dirty content: an edit made while the
correction was in flight bailed out of the debounce effect (savingRef was
held) and, since content isn't a savingRef dependency, was never
rescheduled once the mutex freed — the edit could sit unsaved indefinitely.
The same gap explained a related report that a failed correction which had
already been superseded by a newer edit left saveStatus stuck, since
nothing else was driving it forward.

Fix is one line: call save() in the finally() when content is still dirty.
save()'s own guards (savingRef/discardedRef/content-equality) make this
safe to call unconditionally, and it naturally hands status ownership to
the newer edit's own save cycle.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/hooks/use-autosave.ts Outdated
Comment thread apps/sim/hooks/use-autosave.ts Outdated
…s retryable

Cursor Bugbot round 3 caught 2 more real gaps:

- The document-change reset added last round compared effectiveDraftKey
  (draftKey gated by enabled), so toggling enabled alone for the SAME
  document — e.g. a streaming lock — looked identical to switching files.
  That cleared discardedRef mid-correction, which skipped the correction's
  own setSaveStatus('error'/'idle') (gated on discardedRef to avoid
  clobbering a newer edit's status), silently stranding the hook on
  'saving' with no visible retry affordance. Now keyed off the raw
  draftKey, which enabled toggling never touches.

- After a failed correction, content already equals savedContent (that's
  what discard reverted to), so saveImmediately()'s retry going through
  save() hit its dirty-check and was a complete no-op — the error toast's
  Retry button did nothing. Extracted the correction logic into
  runCorrection(target), shared by discard() and by saveImmediately when a
  failed correction is pending, so retry pushes the reverted baseline
  again instead of bailing on a check that assumes retries are always for
  dirty content.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 76964d4. Configure here.

@waleedlatif1 waleedlatif1 merged commit e2cb3b2 into staging Jul 10, 2026
18 checks passed
@waleedlatif1 waleedlatif1 deleted the files-autosave-flush-fixups branch July 10, 2026 03:05
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