fix(sync): failure-backoff so a dead remote isn't probed every command (#133) - #136
Merged
Conversation
#133) The pre-unlock TTL-gate (#103) only stamped LastPullCheck on a *successful* probe, so with sync enabled and the rclone remote down/slow every command re-probed and blocked for the full 8s probe timeout: Warning: failed to check remote state: rclone lsjson failed: timed out after 8s Fix: a failed probe now stamps a separate LastPullFailure and enters a backoff (reusing the pull_ttl_seconds window, default 30s). While the backoff is active SmartPull skips the probe and serves the local vault, so a dead remote costs the timeout at most once per window instead of on every call. A successful contact clears the failure (stampSuccess), so recovery re-enables normal sync immediately. Data-loss safety: a write during the backoff is DEFERRED, not pushed. Blind- pushing (or "let rclone fail") would silently overwrite a remote that recovered mid-window; deferring keeps the local change local until the next command after the window expires, when a fresh probe can conflict-check. The success-skip path is unchanged (still does its push-time conflict check). Reuses the existing pull_ttl_seconds knob as the backoff window (one knob). A configurable probe timeout (issue option 2) is left as a follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014kVLjbUL4F4CkoA7RbMYy8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #133.
Problem
The pre-unlock TTL-gate (#103) only stamped
LastPullCheckon a successful probe. So with sync enabled and the rclone remote down/slow, every command re-probed and blocked for the full 8s probe timeout:The gate that was supposed to skip re-probing never opened, because a dead remote never records a check.
Fix (issue option 1 — the root cause)
A failed probe now stamps a separate
LastPullFailureand enters a backoff (reusing thepull_ttl_secondswindow, default 30s):SmartPullskips the probe while the backoff is active and serves the local vault → a dead remote costs the timeout at most once per window instead of every call.stampSuccess), so recovery re-enables normal sync immediately.Data-loss safety (the important part)
A write during the backoff is deferred, not pushed. The advisor flagged the trap: the remote can recover mid-window, so "the remote is down, a push can't overwrite anything" is false. Blind-pushing (or letting
rclone syncfail — which is unbounded, not 8s-capped) would silently overwrite a remote that another device updated during the window. Deferring keeps the local change local until the next command after the window expires, when a fresh probe can conflict-check. The existing success-skip path is unchanged (still does its push-time conflict check).Scope
pull_ttl_secondsknob as the backoff window (one knob, no new config).Tests
5 new tests in
internal/sync/sync_test.go:LastPullFailure(notLastPullCheck)Runcalls)SmartPushdefers during backoff: 0 probe calls, 0 push calls,LastPushHashuntouchedFull unit + integration suite green;
golangci-lintclean.🤖 Generated with Claude Code