Skip to content

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

Closed
LLQWQ wants to merge 3 commits into
Go1c:mainfrom
LLQWQ:feat/setting-sync
Closed

fix: make config sync directories configurable via config.yaml#15
LLQWQ wants to merge 3 commits into
Go1c:mainfrom
LLQWQ:feat/setting-sync

Conversation

@LLQWQ
Copy link
Copy Markdown
Contributor

@LLQWQ LLQWQ commented Apr 27, 2026

Bug Description

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

Steps to Reproduce

  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. Observe that .agents/ files are not synced to CLI
  5. Run Obsidian sync again — .agents/ files may be deleted

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)

See PR for implementation.

Environment

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

LLQWQ added 2 commits April 27, 2026 11:59
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
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 27, 2026

Reviewer's Guide

Aligns FileSync, SettingSync, FolderSync, and SyncEngine so that .obsidian and .agents are always treated as config directories, other dot-prefixed directories follow sync_config, and file sync never uploads config directories while folder events for dot-dirs are ignored, preventing accidental deletion or desync of custom config dirs.

Sequence diagram for handling config directory files during sync

sequenceDiagram
    actor User
    participant SyncEngine
    participant FileSync
    participant SettingSync
    participant FolderSync
    participant Server

    User->>SyncEngine: start_sync()
    SyncEngine->>SettingSync: request_sync()
    SettingSync-->>SyncEngine: settings_sync_started
    SyncEngine->>FileSync: request_sync()
    FileSync-->>SyncEngine: file_sync_started

    rect rgb(235, 245, 255)
        note over SettingSync,Server: Upload config file in .agents
        SettingSync->>Server: upload(.agents/agent.json)
        Server-->>SettingSync: ack
    end

    rect rgb(245, 235, 255)
        note over Server,FolderSync: Folder event for .agents directory
        Server-->>FolderSync: FolderSyncModify .agents
        FolderSync->>FolderSync: inspect rel_path
        FolderSync->>FolderSync: first startswith dot
        FolderSync-->>Server: ignore config dir event
    end

    rect rgb(235, 255, 235)
        note over SyncEngine,FileSync: Decide whether to upload file
        SyncEngine->>SyncEngine: _is_config(.agents/agent.json)
        SyncEngine-->>FileSync: is_config = True
        FileSync->>FileSync: _collect_local_files()
        FileSync->>FileSync: skip config file
        FileSync-->>SyncEngine: no upload for .agents
    end

    SyncEngine-->>User: sync_complete
Loading

Class diagram for updated sync components config handling

classDiagram
    class SyncConfig {
        bool sync_files
        bool sync_config
    }

    class SyncEngine {
        +SyncConfig config
        +FileSync file_sync
        +SettingSync setting_sync
        +Path vault_path
        +bool _is_note(rel_path)
        +bool _is_config(rel_path)
        +bool _should_sync_file(rel_path)
        +async _initial_sync()
        +async _push_all_files()
        +bool is_excluded(rel_path)
    }

    class FileSync {
        +SyncEngine engine
        +SyncConfig config
        +list _collect_local_files()
        +async request_sync()
        +async push_upload(rel_path)
    }

    class SettingSync {
        +SyncConfig config
        +async request_sync()
        +async handle_message(msg_data)
    }

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

    class SettingSyncHelpers {
        +dict _extract_inner(msg_data)
        +bool _is_config_path(rel, custom_dirs)
    }

    SyncEngine --> SyncConfig : uses
    FileSync --> SyncEngine : uses
    FileSync --> SyncConfig : uses
    SettingSync --> SyncConfig : uses
    SyncEngine --> FileSync : coordinates
    SyncEngine --> SettingSync : coordinates
    SyncEngine --> FolderSync : coordinates
    SettingSync --> SettingSyncHelpers : uses

    note for SyncEngine "_is_config(rel_path):\n- if first segment not dot-prefixed -> False\n- if first is .obsidian or .agents -> True\n- else -> config.sync.sync_config"
    note for FileSync "_collect_local_files():\n- skip dot-prefixed dirs only when config.sync.sync_config is True\n- never upload config files\n- upload only when config.sync.sync_files is True"
    note for SettingSyncHelpers "_is_config_path(rel, custom_dirs):\n- non dot-prefixed -> False\n- .obsidian or .agents -> True\n- custom_dirs entries -> True\n- all other dot-prefixed dirs -> True (backward compatible)"
    note for FolderSync "_on_sync_modify/delete/rename():\n- ignore events whose first path segment is dot-prefixed\n- prevents folder operations on config dirs"
Loading

Flow diagram for determining config vs file sync handling

flowchart TD
    A["Start with relative path rel"] --> B["first = first path segment"]
    B --> C{first startswith dot?}
    C -->|No| D["Config: False in SyncEngine._is_config\nSettingSync._is_config_path: False"]
    C -->|Yes| E{first is .obsidian or .agents?}

    E -->|Yes| F["Config: True in SyncEngine._is_config\nAlways handled as config"]

    E -->|No| G{Context}

    G -->|SyncEngine / FileSync| H{config.sync.sync_config?}
    H -->|Yes| I["Config: True\nDot-prefixed dir treated as config"]
    H -->|No| J["Config: False\nDot-prefixed dir treated as normal file"]

    G -->|SettingSync| K{first in custom_dirs?}
    K -->|Yes| L["Config: True\nCustom config dir"]
    K -->|No| M["Config: True\nAll other dot dirs treated as config (backward compatible)"]

    subgraph FileSync_behavior
        I --> N["_collect_local_files: skip when config.sync.sync_config is True"]
        F --> N
        M --> N
        J --> O["Normal file: considered for upload when config.sync.sync_files is True"]
        D --> O
    end

    subgraph FolderSync_behavior
        C -->|Yes| P["Ignore FolderSyncModify/Delete/Rename\nfor dot-prefixed directories"]
        C -->|No| Q["Apply folder operation"]
    end
Loading

File-Level Changes

Change Details Files
Make SyncEngine and SettingSync treat .obsidian and .agents explicitly as config while gating other dot-prefixed dirs on sync_config and custom config lists.
  • Refined _is_config in SyncEngine to only treat .obsidian and .agents as config by default and to use sync_config for other dot-prefixed dirs.
  • Updated _is_config_path in SettingSync to always treat .obsidian and .agents as config, support a custom list of config directories, and keep backward compatibility by defaulting all dot-prefixed dirs to config.
fns_cli/sync_engine.py
fns_cli/setting_sync.py
Ensure FileSync never uploads config directories and respects sync_files independently of sync_config.
  • Changed FileSync _collect_local_files to skip dot-prefixed directories only when sync_config is enabled and to rely solely on sync_files for non-config uploads.
  • Adjusted SyncEngine _push_all_files to skip config paths entirely and only push files when sync_files is enabled, and simplified initial sync to gate FileSync only on sync_files.
fns_cli/file_sync.py
fns_cli/sync_engine.py
Prevent FolderSync from applying folder operations to config directories managed by SettingSync.
  • Added guards in FolderSync modify, delete, and rename handlers to ignore dot-prefixed directories and log debug messages instead of modifying them locally.
fns_cli/folder_sync.py

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:

  • The new FolderSync filters (_on_sync_modify/delete/rename) treat all dot-prefixed directories as config regardless of sync_config, which is more aggressive than _is_config; consider reusing the same helper/logic so future changes to config-dir semantics don’t diverge between components.
  • Config-directory detection is now implemented in several places (SyncEngine._is_config, SettingSync._is_config_path, FolderSync handlers, FileSync._collect_local_files) with slightly different rules; centralizing this into a shared helper would reduce the risk of subtle inconsistencies over time.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new FolderSync filters (`_on_sync_modify/delete/rename`) treat all dot-prefixed directories as config regardless of `sync_config`, which is more aggressive than `_is_config`; consider reusing the same helper/logic so future changes to config-dir semantics don’t diverge between components.
- Config-directory detection is now implemented in several places (`SyncEngine._is_config`, `SettingSync._is_config_path`, FolderSync handlers, `FileSync._collect_local_files`) with slightly different rules; centralizing this into a shared helper would reduce the risk of subtle inconsistencies over time.

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.

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
@LLQWQ LLQWQ force-pushed the feat/setting-sync branch from 1e60a06 to b12b7b8 Compare April 27, 2026 06:19
@LLQWQ LLQWQ changed the title fix: ensure custom config dirs (.agents) are handled by SettingSync fix: make config sync directories configurable via config.yaml Apr 27, 2026
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.

New security issues found

Comment thread config.yaml
token: "your_api_token"
vault: "defaultVault"
api: "http://127.0.0.1:9000"
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsIm5pY2tuYW1lIjoiaHViaW4iLCJpcCI6IjE4MC4xNTIuNjUuOTkiLCJpc3MiOiJmYXN0LW5vdGUtc3luYy1zZXJ2aWNlIiwic3ViIjoidXNlci10b2tlbiIsImV4cCI6MTgwNzQxMzM0MCwibmJmIjoxNzc1ODc3MzQwLCJpYXQiOjE3NzU4NzczNDAsImp0aSI6IjEifQ.OgUOJXHrxxdNF9pv0tY0j5_qhBMsZsyaD_ByHqAgVtw"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security (jwt): Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

Source: betterleaks

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