Skip to content

fix(sync): remote wins on first sync for create_if_missing files#6

Closed
stefanzvonar wants to merge 1 commit intopaddo-tech:mainfrom
stefanzvonar:fix/create-if-missing-remote-wins
Closed

fix(sync): remote wins on first sync for create_if_missing files#6
stefanzvonar wants to merge 1 commit intopaddo-tech:mainfrom
stefanzvonar:fix/create-if-missing-remote-wins

Conversation

@stefanzvonar
Copy link
Copy Markdown
Collaborator

Why

When a dotfile with create_if_missing = true is added to the config and synced to a new machine, the remote content may not be applied — leaving the file empty or with a default value instead of the synced content.

This happens when an application creates the file before tether runs (e.g. Claude Code writes {} to ~/.claude/settings.json on startup). Tether's conflict detection treats that locally-created default as a "local edit" and refuses to overwrite it with the remote version.

As-Is

  1. Machine A adds .claude/settings.json (with real content) to tether config and syncs
  2. Machine B runs tether sync — config is pulled, file doesn't exist locally
  3. Before decrypt_from_repo writes the remote content, the app creates a default file (e.g. {})
  4. detect_conflict() sees: local exists ({}), no last_synced_hash, local ≠ remote → true conflict
  5. In daemon mode: conflict is skipped. In interactive mode: user is prompted for a file they never edited
  6. Even without a conflict, the "no conflict" path checks local_unchanged = (local_hash == last_synced_hash)Some(hash) == Nonefalse → remote not written

Both code paths fail to apply remote content when there's no sync history for the file.

To-Be

When create_if_missing = true AND there's no sync history for the file on this machine (last_synced_hash is None), treat it as a first-time sync where remote wins:

  1. Skip conflict detection entirely — there's no local state worth preserving
  2. Write remote content unless local already matches remote (idempotent)
  3. Back up any existing local file before overwriting (safety net)
  4. Create parent directories if needed

After the first sync completes, subsequent syncs use the normal three-way conflict detection since last_synced_hash will be populated.

Test plan

  • cargo clippy --all-targets -- -D warnings passes
  • cargo test passes
  • Add .claude/settings.json with create_if_missing = true on Machine A, sync
  • On Machine B (where Claude Code already created {}), run tether sync → file should have Machine A's content
  • On Machine B, edit the file, sync → local edit preserved (normal conflict detection)
  • On Machine C (file doesn't exist at all), run tether sync → file created with remote content

When a dotfile has no sync history on a machine but exists locally
(e.g. an app creates a default before tether runs), conflict detection
treated it as a local edit and refused to apply remote content.
@stefanzvonar stefanzvonar marked this pull request as ready for review April 3, 2026 11:53
@stefanzvonar stefanzvonar requested a review from paddo April 3, 2026 11:54
stefanzvonar added a commit that referenced this pull request Apr 4, 2026
When a dotfile has create_if_missing=true and no sync history, skip
conflict detection and let remote content win. This handles the case
where an app creates a default file before tether runs (e.g. Claude Code
writing {} to settings.json on startup).

For files without create_if_missing, local remains authoritative when
there's no sync history (from the is_true_conflict fix).

Incorporates the fix from PR #6.
@stefanzvonar
Copy link
Copy Markdown
Collaborator Author

Superseded by #8 which combines this fix with additional fixes for the same issue area (is_true_conflict + effective_dotfiles merge).

paddo pushed a commit that referenced this pull request Apr 7, 2026
When a dotfile has create_if_missing=true and no sync history, skip
conflict detection and let remote content win. This handles the case
where an app creates a default file before tether runs (e.g. Claude Code
writing {} to settings.json on startup).

For files without create_if_missing, local remains authoritative when
there's no sync history (from the is_true_conflict fix).

Incorporates the fix from PR #6.
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