Skip to content

id(web): AutoSaveManager's generic-error path drops a coalesced follow-up — an edit made during a failed save is never autosaved #1616

Closed

Description

@dezren39

Problem

AutoSaveManager (pkgs/id/web/src/auto-save.ts, added by PR #1545 for #1440) coalesces a save requested while one is in flight into exactly one follow-up — but only two of its three result paths honour that follow-up:

  • ok (onSaveResult, auto-save.ts:161-169): runs the follow-up via triggerSave().
  • rate-limited (auto-save.ts:180-191): schedules a retry that "also serves as the follow-up for any edits that arrived during the failed save".
  • generic error (auto-save.ts:192-206): sets state error, reverts to unsaved after 2s — and never acts on followUpRequested, and never clears it. No debounce, retry, or follow-up is pending at that point (the during-flight edit's debounce already fired and was consumed by the in-flight triggerSave at auto-save.ts:124-128), so autosave silently stops for that edit: the document sits at save • until the user happens to edit again or saves manually.

Evidence (verified this run)

Probe driving the real auto-save.ts on the laptop (~/git/fleet/probe-2026-10-28-laneB-autosave there; file sha256 ba898143929b3192ebc4f4b236a5c8d6fe09569aee83df715fc9e6fa6fab01a9, byte-identical to main b2adaf4; probe + log in the goal's hidden_files/probe-2026-10-28-laneB-autosave/), run twice with identical results. Per case: edit → debounce fires → save #1 in flight; second edit during the flight; its debounce also fires during the flight (follow-up coalesced); then save #1 settles:

  • CTRL-OK: calls after settle = 2 (follow-up ran), state saved/idle
  • CTRL-429 (retryAfterMs: 100): calls after settle = 2 (retry ran), state saved/idle
  • BUG-ERROR ({ok: false}): calls after settle = 1, and still 1 after a further 3s wait; state = unsaved — the during-flight edit is stranded, no save is scheduled

Relevant code: coalescing at auto-save.ts:124-135 (followUpRequested = true; return), ok-branch follow-up at :162-168, retry-branch follow-up at :180-191, error branch at :192-206 (no follow-up, flag left set). A throwing saveFn lands in the same branch (auto-save.ts:151-155).

Note the stranded case is specifically when the follow-up was already consumed before the failure: an edit whose debounce is still pending when the error lands does get saved when that debounce fires. The test suite from #1440 (auto-save.test.ts) covers the ok and 429 follow-up shapes but has no generic-error case.

Why it matters

Autosave's contract after #1440 is "a save completion only marks the document saved when it covers the latest edit, and requested saves are never lost". The generic-error path breaks the second half silently: a transient failure (network blip, 500) during a save eats the next edit's autosave, and the only signal is the button reverting to save • — indistinguishable from "waiting for the debounce". The user can close the tab believing the 2s autosave had long since fired.

Acceptance criteria

  • Pin the generic-error follow-up semantics in a test first: an edit that arrives during a save whose debounce fires before that save fails generically must not be stranded — it gets exactly one subsequent save attempt (a follow-up, or a re-armed debounce), OR the manager documents and tests a deliberate no-auto-retry pin in which the follow-up request is visibly dropped (flag cleared, state/behaviour intentional).
  • If a follow-up is implemented: it is bounded (one attempt; a second generic failure stops again — no retry loop on a persistent outage), and followUpRequested is cleared on every onSaveResult path.
  • New test in auto-save.test.ts mirroring the existing ok/429 serialization tests for the generic-error shape (deferred saveFn, edit + debounce during flight, resolve {ok: false}, assert the follow-up/strand behaviour pinned above).
  • Existing auto-save tests (serialization, stale-completion, 429 coalescing, saveNow) still pass.

Related: #1440 (closed by PR #1545 — this is the one result path its serialization work did not cover). Not a duplicate of #1368 (collab reconnect cap — different module/mechanism).

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/triageQueue packet kind: verify, reproduce, and route this item
    source/creatorFiled by the new-issue-creator job
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 28, 2026
  2. developing-today commented on Oct 28, 2026

    @developing-today
    Contributor
  3. added
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    effort/sSmall: expected under 2 hours of work
    on Oct 28, 2026
  4. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · state: filed

    • Agent: new-issue-creator-code-b (new-issue-creator job, lane B)
    • Session / ID: new-issue-creator-code-b run 2026-10-28 02:20 CDT
    • Found via: filed this run
    • Task: Filed after verifying on the laptop with a probe driving the real auto-save.ts: the generic-error branch of onSaveResult drops a follow-up coalesced during a failed save, stranding that edit unsaved with no save scheduled (ok and 429 paths both honour the follow-up; controls in the probe).
    • Expectation: effort/s — queue-lane triage pins the error-path follow-up semantics (bounded follow-up or re-armed debounce vs a deliberate tested no-retry pin), adds the missing generic-error test to auto-save.test.ts.
    • Links: probe + log in the goal workspace (hidden_files/probe-2026-10-28-laneB-autosave/); related id(web): AutoSaveManager runs concurrent saves and a stale completion marks newer edits saved #1440 / PR fix(id-web): serialize AutoSaveManager saves + stale-completion guard (#1440) #1545
  5. added
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    and removed on Oct 28, 2026
  6. 47 remaining items

  7. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

    • Agent: queue worker (repo-worker-code), Lane A shift
    • Session / ID: queue task 1855 (resume)
    • Found via: queue claim
    • Task: Re-parked — PR 1712 CI still pending after poll window: 17 pass / 14 pending / 0 fail; pending jobs remain queued (fleet CI stall, same as prior park). Relevant checks already pass (test-web-typecheck, biome, guard list); laptop verification in PR body (web suite 454 passed, tsc clean).
    • Expectation: resume when CI completes; merge if green.
    • Links: PR id(web): run one bounded follow-up for edits coalesced during a generically failed save (#1616) #1712
  8. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Oct 28, 2026
  9. removed
    task/triageQueue packet kind: verify, reproduce, and route this item
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    work/assignedQueue worker lifecycle state
    on Oct 28, 2026
  10. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done — PR merged

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/idPackage/app: id (pkgs/ or apps/ in this repo)effort/sSmall: expected under 2 hours of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobstatus:done

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions