Skip to content

fix: make config sync directories configurable via config.yaml#18

Merged
Go1c merged 4 commits into
Go1c:mainfrom
LLQWQ:feat/config-sync-fix-v2
May 4, 2026
Merged

fix: make config sync directories configurable via config.yaml#18
Go1c merged 4 commits into
Go1c:mainfrom
LLQWQ:feat/config-sync-fix-v2

Conversation

@LLQWQ
Copy link
Copy Markdown
Contributor

@LLQWQ LLQWQ commented Apr 27, 2026

Problem

CLI's FileSync and SettingSync have inconsistent handling of dot-prefixed directories like .agents:

  • FileSync._collect_local_files() skips ALL dot-prefixed directories
  • SettingSync uses _is_config_path() which treats ALL dot-prefixed dirs as config
  • But SyncEngine._is_config() also treats ALL dot-prefixed dirs as config

This causes problems when syncing custom config directories:

  1. If .agents/ exists on Obsidian but not on CLI vault, CLI won't send it to server
  2. Server may then tell other devices to delete .agents/ files
  3. Custom config directories get lost across devices

Impact

  • Custom config directories (.agents, .my-config, etc.) are not properly synchronized
  • Data loss when using CLI alongside Obsidian plugin
  • Inconsistent behavior between FileSync and SettingSync

Root Cause

In file_sync.py:

first = rel.split("/")[0]
if first.startswith("."):
    continue  # Skips .agents/ files

In sync_engine.py:

def _is_config(self, rel_path: str) -> bool:
    first = rel_path.split("/")[0]
    return first.startswith(".")  # Too broad

Proposed Fix

Make config directory handling explicit:

  1. Always treat .obsidian and .agents as config directories
  2. Let other dot-prefixed dirs follow sync_config setting
  3. FileSync should skip dot-prefixed dirs only when sync_config=True
    (so they go through SettingSync)

Files Changed

  • fns_cli/config.py: Add config_sync_dirs to config schema
  • fns_cli/file_sync.py: Skip dot-prefixed dirs based on config_sync_dirs
  • fns_cli/setting_sync.py: Use config_sync_dirs for config path check
  • fns_cli/sync_engine.py: Use explicit config directory list

Testing

  1. Set up Obsidian plugin with .agents in configSyncOtherDirs
  2. Add files to .agents/ directory in Obsidian
  3. Run CLI sync on another device (without .agents/ directory)
  4. Verify .agents/ files are synced to CLI
  5. Run Obsidian sync again — .agents/ files should remain

Environment

  • CLI version: latest (feat/setting-sync branch)
  • OS: Linux/Any

Summary by Sourcery

Make config directories explicitly configurable and ensure only configured dot-prefixed folders are treated as settings, aligning FileSync, SettingSync, FolderSync, and SyncEngine behavior.

New Features:

  • Add configurable config_sync_dirs option to control which dot-prefixed directories are treated as config/settings.

Bug Fixes:

  • Prevent non-config dot-prefixed directories from being misclassified as config, avoiding unintended sync or deletion.
  • Ensure FolderSync ignores config directories so they are managed exclusively by SettingSync.
  • Stop file uploads of config directories when only config or only files syncing is enabled, respecting sync_files and sync_config flags.

Enhancements:

  • Align FileSync, SettingSync, FolderSync, and SyncEngine logic around config directory detection for consistent behavior across the CLI.

LLQWQ added 3 commits April 27, 2026 15:06
Config files (.obsidian/*, .agents/*, etc.) should only be synced via
SettingSync protocol, not FileSync. This fixes three issues:

1. _initial_sync(): only call file_sync.request_sync() when sync_files
   is true, not when sync_config is true
2. _push_all_files(): skip config files entirely, let _push_all_settings
   handle them via SettingSync
3. file_sync._collect_local_files(): always skip dot-prefixed directories,
   regardless of sync_config setting

Previously, config files were uploaded to both file DB and setting DB,
causing conflicts and plugin loss during sync.
Config directories (.obsidian, .agents, etc.) are managed by SettingSync
protocol, not FolderSync. This prevents accidental deletion of the entire
.obsidian directory when the server sends FolderSyncDelete after config
records are removed from the file database.

The fix adds checks in all three FolderSync handlers:
- _on_sync_modify: skip creating dot-prefixed dirs
- _on_sync_delete: skip deleting dot-prefixed dirs (CRITICAL)
- _on_sync_rename: skip renaming dot-prefixed dirs
Previously, FileSync and SettingSync had inconsistent hardcoded handling
of dot-prefixed directories. This caused custom config directories like
.agents to not be properly synchronized.

Changes:
- config.py: add config_sync_dirs field to SyncConfig (default: [.obsidian, .agents])
- sync_engine.py: use config_sync_dirs from config instead of hardcoded list
- setting_sync.py: pass config_sync_dirs to _is_config_path()
- file_sync.py: skip dot-prefixed dirs only when sync_config is enabled
- config.yaml: add config_sync_dirs example configuration

Users can now customize which dot-prefixed directories are treated as config
by editing config.yaml:

  config_sync_dirs:
    - .obsidian
    - .agents
    - .my-custom-config

Fixes: custom config directories (.agents) not being synced by CLI
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 27, 2026

Reviewer's Guide

Makes config directories explicitly configurable via config.yaml and routes dot-prefixed directories consistently through SettingSync instead of FileSync/FolderSync, using a new config_sync_dirs list and updated config detection logic across the sync engine.

Sequence diagram for routing dot-prefixed config directories through SettingSync

sequenceDiagram
    actor User
    participant CLI as CliEntryPoint
    participant Engine as SyncEngine
    participant FSync as FileSync
    participant SSync as SettingSync
    participant Folder as FolderSync

    User->>CLI: run sync
    CLI->>Engine: start _initial_sync()

    Engine->>Engine: _is_note(rel_path)
    alt rel_path is note
        Engine->>Engine: schedule note sync
    else rel_path is not note
        Engine->>Engine: _is_config(rel_path)
        Engine->>Engine: first = rel_path.split("/")[0]
        alt first not dot-prefixed
            Engine-->>Engine: _is_config returns False
            Engine->>FSync: handle as regular file
        else first dot-prefixed
            Engine->>Engine: check config.sync.config_sync_dirs
            alt first in config_sync_dirs
                Engine-->>Engine: _is_config returns True
                Engine->>SSync: manage via SettingSync
                note over SSync,FSync: FileSync and FolderSync skip this path
            else first not in config_sync_dirs
                Engine->>Engine: return config.sync.sync_config
                alt sync_config is True
                    Engine-->>Engine: _is_config returns True
                    Engine->>SSync: manage via SettingSync
                else sync_config is False
                    Engine-->>Engine: _is_config returns False
                    Engine->>FSync: manage via FileSync
                end
            end
        end
    end

    par folder updates from server
        Engine->>Folder: _on_sync_modify/_delete/_rename(rel_path)
        Folder->>Folder: first = rel_path.split("/")[0]
        alt first startswith(".")
            Folder-->>Engine: ignore, config dir handled by SettingSync
        else
            Folder->>Folder: apply filesystem change
        end
    end
Loading

Class diagram for updated sync configuration and routing

classDiagram
    class SyncConfig {
        list~str~ exclude_patterns
        int file_chunk_size
        list~str~ config_sync_dirs
        bool sync_files
        bool sync_config
    }

    class AppConfig {
        SyncConfig sync
    }

    class SyncEngine {
        AppConfig config
        Path vault_path
        FileSync file_sync
        SettingSync setting_sync
        NoteSync note_sync
        bool is_excluded(rel_path: str)
        bool _is_note(rel_path: str)
        bool _is_config(rel_path: str)
        bool _should_sync_file(rel_path: str)
        async _initial_sync()
        async _push_all_files()
    }

    class FileSync {
        AppConfig config
        SyncEngine engine
        list~dict~ _collect_local_files()
        async request_sync()
        async push_upload(rel_path: str)
    }

    class SettingSync {
        AppConfig config
        async request_sync()
    }

    class FolderSync {
        Path vault_path
        async _on_sync_modify(msg: WSMessage)
        async _on_sync_delete(msg: WSMessage)
        async _on_sync_rename(msg: WSMessage)
    }

    class ConfigLoader {
        AppConfig load_config(path: str)
    }

    AppConfig *-- SyncConfig
    SyncEngine *-- FileSync
    SyncEngine *-- SettingSync
    SyncEngine --> AppConfig
    FileSync --> SyncEngine
    FileSync --> AppConfig
    SettingSync --> AppConfig
    FolderSync --> SyncEngine
    ConfigLoader --> AppConfig
    SyncConfig "1" o-- "many" str : config_sync_dirs
Loading

File-Level Changes

Change Details Files
Introduce configurable config_sync_dirs list in CLI sync configuration and load it from config.yaml.
  • Add config_sync_dirs field to SyncConfig dataclass with default ['.obsidian', '.agents'].
  • Wire config_sync_dirs loading from config.yaml in load_config().
fns_cli/config.py
Align FileSync behavior so config directories are skipped when config syncing is enabled, independent of file sync flag.
  • Update _collect_local_files() to skip dot-prefixed dirs only when sync_config is enabled, allowing SettingSync to own them.
  • Simplify file sync gating to depend solely on sync_files for non-config paths.
fns_cli/file_sync.py
Use explicit config directory list in SyncEngine to decide which paths are treated as config and adjust initial and full sync behavior accordingly.
  • Refine _is_config() to check config_sync_dirs first and fall back to sync_config for other dot-prefixed dirs.
  • Change _initial_sync() so FileSync runs only when sync_files is enabled, decoupling from sync_config.
  • Simplify _push_all_files() to upload only non-config files and gate them solely on sync_files.
fns_cli/sync_engine.py
Allow SettingSync to respect configured config_sync_dirs while remaining backward compatible with previous behavior.
  • Extend _is_config_path() to accept a config_sync_dirs parameter and use it to decide if a dot-prefixed path is config.
  • Keep default behavior that all dot-prefixed dirs are treated as config when no explicit list is provided.
fns_cli/setting_sync.py
Ensure FolderSync ignores operations on config directories (dot-prefixed folders) because they are managed by SettingSync.
  • Add early returns in _on_sync_modify(), _on_sync_delete(), and _on_sync_rename() when paths start with a dot-prefixed directory.
  • Log debug messages when folder sync operations are skipped for config dirs.
fns_cli/folder_sync.py

Possibly linked issues

  • #(unknown): PR implements configurable config_sync_dirs and adjusts FileSync/SettingSync/SyncEngine to correctly sync dot-prefixed config dirs.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • FolderSync now unconditionally ignores all dot-prefixed directories, which bypasses the new config_sync_dirs/sync_config logic and may prevent non-config dot directories (or config directories when sync_config is disabled) from ever syncing; consider aligning FolderSync’s behavior with _is_config/config_sync_dirs instead of hard-coding first.startswith('.').
  • The default values for config_sync_dirs are duplicated in both the SyncConfig dataclass and load_config; centralizing this default (e.g., via a shared constant or using the dataclass default when config is missing) would reduce the risk of these ever diverging.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- FolderSync now unconditionally ignores all dot-prefixed directories, which bypasses the new `config_sync_dirs`/`sync_config` logic and may prevent non-config dot directories (or config directories when `sync_config` is disabled) from ever syncing; consider aligning FolderSync’s behavior with `_is_config`/`config_sync_dirs` instead of hard-coding `first.startswith('.')`.
- The default values for `config_sync_dirs` are duplicated in both the `SyncConfig` dataclass and `load_config`; centralizing this default (e.g., via a shared constant or using the dataclass default when config is missing) would reduce the risk of these ever diverging.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

- Centralize DEFAULT_CONFIG_SYNC_DIRS constant in config.py to avoid duplication
- Make FolderSync._is_config_dir() respect config_sync_dirs and sync_config settings
  instead of hard-coding first.startswith('.') check

Closes review comments from sourcery-ai
@LLQWQ
Copy link
Copy Markdown
Contributor Author

LLQWQ commented Apr 27, 2026

@sourcery-ai Thanks for the review! Both issues have been addressed in commit 1b84efb:

  1. FolderSync hard-coding: Replaced with method that uses the same logic as — checking first, then falling back to flag. This ensures non-config dot directories (or config directories when ) are properly handled.

  2. Default values duplication: Introduced constant in , used by both the dataclass default and fallback.

Please re-review when convenient.

@Go1c Go1c merged commit 391e05f into Go1c:main May 4, 2026
4 checks passed
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.

2 participants