---
name: syncthing-folder-sync
description: Set up continuous bidirectional folder sync between the Hostinger VPS (2.25.172.164) and Windows laptop "Connie" using Syncthing, for cases where a folder must live on the VPS as single-source-of-truth but be readable/writable from Windows (and eventually a tablet). Trigger when the user asks to keep an Obsidian vault, project folder, or any working tree in sync across machines — NOT for one-off transfers (use windows-to-vps-file-transfer for those). Covers the systemd-managed VPS side, the Windows winget side, pairing by device ID, the folder-share handshake, and the pitfalls that come up (PATH staleness, firewall prompts, relay fallback, .stfolder marker).
---

# Syncthing Folder Sync — VPS ↔ Connie

Continuous bidirectional sync between the VPS and Windows. Use this when a folder needs to be **the same** on both sides over time (Obsidian vault, active project tree, shared drop). **For one-shot pushes, use `windows-to-vps-file-transfer` instead** — Syncthing is overkill for a single `scp`.

This user's architecture: VPS (Hostinger, 2.25.172.164) = single source of truth, Connie (Windows laptop) = client. The vault/folder should live on the VPS so the Hermes agent can read/write it directly; Connie syncs to it.

## When this skill fires

- "I want my Obsidian vault on both machines"
- "Keep X in sync between the VPS and the laptop"
- "I saved ideas on Connie / tablet / wherever — how do they get into the vault?"
- Any "single folder, multiple writers, multiple readers" topology

**Do NOT fire for:**
- One-time file moves → `windows-to-vps-file-transfer`
- Diagnosing a Windows exe that won't start → `windows-launch-diagnostics`
- Verifying a user-claimed setup → `verifying-user-claims`

## Topology decision (make this first, explicitly)

Before installing anything, ask: **which side is the source of truth?** For this user the answer is almost always **VPS**, because:

1. The Hermes agent runs on the VPS and needs direct read/write
2. Cron jobs on the VPS can file notes into the vault automatically
3. The VPS is always on; Connie sleeps
4. Single-source-of-truth is the user's stated architectural bias

The Windows side then becomes a synced replica. Obsidian on Connie opens the synced folder; writes flow back to the VPS within seconds.

If the user pushes back and wants Connie-as-truth, that's fine — the Syncthing setup is symmetric. But call out the trade-off explicitly (agent can't read the vault when Connie is off, cron jobs can't file into it).

## Separate shared working folders (not Obsidian)

Syncthing folders do not need to live inside an Obsidian vault. When Rob wants a file exchange area that Hermes and Windows can both use, create a separate folder share rather than placing working artifacts in the vault.

Recommended default topology:

- VPS source-of-truth: `/root/.hermes/shared`
- Connie replica: `C:\Users\Rob\Hermes Shared`
- Label: `Hermes Shared`
- Folder ID: `hermes-shared`
- Type: `sendreceive`
- Devices: Connie only unless Rob explicitly requests another device
- VPS file versioning: enable simple versioning and retain a modest history (10 versions is a reasonable default)

This is a collaboration/drop folder, not an Obsidian workspace. It is appropriate for generated DOCX/PDF files, drafts, exports, scripts, and files Rob asks Hermes to inspect. Keep secrets, API tokens, passwords, and raw credential files out of it.

**Credential preflight — CRITICAL pitfall (hit 2026-07-26):** the agent itself writes `nous_auth.json` to `<hermes-root>/shared/nous_auth.json` by design (so multiple named profiles share OAuth credentials). On this VPS that lands at `/root/.hermes/shared/nous_auth.json` — the SAME path as the default `hermes-shared` Syncthing folder. Result: OAuth refresh tokens get broadcast to every paired device on every token refresh (every 6h). The preflight is NOT complete just by inspecting existing files; the agent's own write path must be redirected BEFORE the first rescan, or credential leakage is automatic and silent.

Before creating or rescanning any shared directory that overlaps `<hermes-root>/shared/`:

1. Check whether `HERMES_SHARED_AUTH_DIR` is set in `/root/.hermes/.env`. If not, the agent will write to `<hermes-root>/shared/nous_auth.json` on the next refresh.
2. If unset, redirect the agent's auth store to a non-synced path BEFORE the first rescan: `echo 'HERMES_SHARED_AUTH_DIR=/root/.hermes/auth-store' >> /root/.hermes/.env && mkdir -p /root/.hermes/auth-store && chmod 700 /root/.hermes/auth-store` and move any pre-existing `nous_auth.{json,lock}` out of the shared folder.
3. Create `.stignore` covering `nous_auth*`, `**/.env`, `**/*.pem`, `**/*.key`, `**/*credentials*` as a belt-and-braces measure (`.stignore` alone is not sufficient because it only blocks FUTURE scans, not already-indexed files).
4. Trigger a rescan and verify the Syncthing database reports only intended files (`needTotalItems=0`, `needBytes=0`, `state=idle`). A green "Up to Date" badge is not sufficient if unexpected files were indexed.
5. After moving credentials, ALSO trigger a Syncthing rescan so the deletion propagates to paired devices — otherwise the leaked file persists on Connie/Surface even after the VPS copy is gone.

The full redirect recipe (chmod, `HERMES_SHARED_AUTH_DIR` env var, gateway restart, verification) is in `references/nous-auth-store-redirect.md`. Apply this every time you create or audit a `hermes-shared`-style folder on a system that runs the agent.

### Auditing an EXISTING shared folder for the leak (3 checks, ~10 seconds)

The preflight above is written for new setups. When you inherit or revisit an existing `hermes-shared` folder, audit it with:

```bash
# 1. Is the redirect env var set? (absent = agent still writes into the synced folder)
grep HERMES_SHARED_AUTH_DIR /root/.hermes/.env || echo "NOT SET — leak path open"

# 2. Is the token file physically sitting in the synced folder right now?
ls -la /root/.hermes/shared/nous_auth*

# 3. Is that folder actually shared to devices? (folder IDs + paths)
API=$(grep -oP 'apikey>\K[^<]+' /root/.local/state/syncthing/config.xml)
curl -s -H "X-API-Key: $API" http://127.0.0.1:8384/rest/config/folders | grep -oE '"id":"[^"]+"|"path":"[^"]+"'
```

If (1) is unset AND (2) exists AND (3) shows the folder shared: the token is being broadcast to every paired device on every refresh (~6h), even if `.stignore` already contains `nous_auth*` — the ignore rule only blocks future scans, and the file was indexed before the rule existed. **This exact state was found live on the VPS on 2026-08-06**: `.stignore` correct since 2026-07-20, `nous_auth.json` still present and syncing, `HERMES_SHARED_AUTH_DIR` never set — the 2026-07-26 fix was never actually applied, only the ignore-rule half of it. A correct-looking `.stignore` is NOT evidence the leak is closed; check all three. Apply `references/nous-auth-store-redirect.md` immediately — and remember the fix is incomplete until the rescan propagates the deletion to the paired devices.

### VPS-first artifact delivery

When Hermes is running on the VPS and creates a file for Rob, the file is not delivered merely because it exists in `/root/.hermes/outbox`. Use one or both of these delivery paths:

1. Attach the file in the current chat using the platform's file-delivery convention.
2. Copy it into the synced shared folder for Windows access, preferably under a task subfolder such as `/root/.hermes/shared/Job Seeker/`.

Do not tell Rob to look in a Windows path corresponding to the VPS outbox, and do not default to a manual PowerShell copy when the shared folder is already available. After copying, verify the destination exists, has a reasonable size, and matches the source checksum. For the Syncthing side, confirm the intended device is connected and `/rest/db/status?folder=hermes-shared` reports `needTotalItems=0`, `needBytes=0`, and `state=idle` before claiming the file is available on Connie.

A separate shared-folder setup and verification recipe is in `references/separate-shared-folder.md`. The OAuth-redirect fix for when `nous_auth.json` is already being broadcast through the shared folder is in `references/nous-auth-store-redirect.md` (apply BEFORE the first rescan on new setups; apply as cleanup on existing setups).

## VPS side (do this first)

```bash
# Install (Ubuntu 24.04 package is fine — 1.27.x at time of writing)
DEBIAN_FRONTEND=noninteractive apt-get install -y syncthing

# Enable + start as a systemd service for root (survives reboots)
systemctl enable --now syncthing@root

# Verify
systemctl is-active syncthing@root          # expect: active
ss -tlnp | grep -E '8384|22000'             # 8384 = GUI (localhost), 22000 = sync port
```

**Expected state after install:**
- Sync port **22000** listening on `0.0.0.0` (reachable from outside)
- GUI on **127.0.0.1:8384** (localhost only — secure default, don't expose it)
- Config + keys at `/root/.local/state/syncthing/` (note: NOT `~/.config/syncthing` — Debian's systemd unit uses the newer state dir)

**Get the VPS device ID** (you'll need to give this to the user):

```bash
grep -oP 'device id="\K[^"]+' /root/.local/state/syncthing/config.xml | head -1
```

Output looks like: `7KUM336-4B22UB6-NIHR6TO-6M4ASSN-DUAQ5BQ-VGZXG2D-IZ7WMWR-SAMJHQL`

**Firewall / port reachability:** the Hostinger VPS needs inbound TCP 22000 open. If the user is behind Hostinger's firewall panel, open it there. If not open, Syncthing falls back to public relays — works but much slower. Test after pairing; if sync is glacial, this is the first thing to check.

## Windows side (Connie)

```powershell
# Install
winget install Syncthing.Syncthing

# CRITICAL: the installer says "Path environment variable modified; restart your shell"
# The CURRENT PowerShell window will NOT see the new `syncthing` command.
# Close the window, open a fresh PowerShell, then:
syncthing
```

**First-run behavior:**
- Generates keys + config at `C:\Users\Rob\AppData\Local\Syncthing\`
- Opens browser to `http://127.0.0.1:8384`
- Prompts to set a GUI password — do it (local-only but good hygiene)
- **Windows Firewall prompt** will pop asking about Public + Private network access. **Allow both.** Private is needed for LAN peers; Public is fine because Syncthing traffic is TLS-encrypted and requires explicit device-ID pairing — random internet hosts can't connect even with the port open.

**The PowerShell window stays blocked while Syncthing runs.** Minimize it, don't close it. For long-term use, set up auto-start (Scheduled Task or the Syncthing "Start on login" option in the GUI) — but get pairing working first.

**Get Connie's device ID:** in the GUI → Actions (top right) → Show ID. Have the user paste it back to you.

## Pairing (the handshake)

Both sides must explicitly add each other. The path actually verified end-to-end on 2026-07-19 is the **REST API** — the `syncthing cli` subcommand syntax varies by build and was NOT validated here. Get the VPS API key first (needed for every REST call):

```bash
grep -oP 'apikey>\K[^<]+' /root/.local/state/syncthing/config.xml
```

Then:

1. **User gives you Connie's device ID** (Connie GUI → Actions → Show ID).
2. **On the VPS, add Connie as a device:**
   ```bash
   API="<apikey from above>"
   curl -s -X POST -H "X-API-Key: $API" -H "Content-Type: application/json" \
     http://127.0.0.1:8384/rest/config/devices \
     -d '{"deviceID":"<CONNIE_ID>","name":"Connie","addresses":["dynamic"],"compression":"metadata","introducer":false,"paused":false}'
   ```
3. **Connie's GUI pops "Add device?"** for the VPS ID → user clicks **Add device** → Save.
4. **Share the folder from the VPS** (two-way = `"type":"sendreceive"`):
   ```bash
   curl -s -X POST -H "X-API-Key: $API" -H "Content-Type: application/json" \
     http://127.0.0.1:8384/rest/config/folders \
     -d '{"id":"hermes-vault","label":"Hermes Vault","path":"/root/.hermes/vault","type":"sendreceive","devices":[{"deviceID":"<CONNIE_ID>"},{"deviceID":"<VPS_ID>"}],"rescanIntervalS":3600,"fsWatcherEnabled":true,"paused":false}'
   ```
5. **Connie's GUI pops "Folder shared with you?"** → user clicks **Add** → the one decision: **the folder path on Connie.** Point it at the existing vault path (`C:\Users\Rob\Documents\Obsidian Vault`) to overlay+merge, or a new folder for a clean start. Both sides end up with the union.

**Verify registration:**
```bash
curl -s -H "X-API-Key: $API" http://127.0.0.1:8384/rest/config/devices
curl -s -H "X-API-Key: $API" http://127.0.0.1:8384/rest/config/folders
```

(Full session transcript with exact IDs + responses: `windows-to-vps-file-transfer` → `references/syncthing-sync.md`.)

## Hermes ↔ Claude Desktop bridge (established 2026-08-06)

Rob's real Claude work lives in **Claude Desktop Projects**, not Claude Code — so the bridge is vault-based shared memory, not transcript mirroring:

- `Hermes Activity/` in the vault — Hermes writes session digests (`YYYY-MM-DD_topic.md`), Claude reads
- `Claude Activity/` in the vault — Claude writes via filesystem MCP server, Hermes reads
- Claude Desktop gets vault access via a **local stdio MCP server** (`@modelcontextprotocol/server-filesystem`) in `claude_desktop_config.json`, pointed at Connie's synced replica `C:\Users\Rob\Documents\Obsidian Vault`
- Rob adds "check Hermes Activity/ before substantive work" to each Project's instructions manually
- Asynchronous by design. Live delegation (remote MCP connector) is deferred — `hermes mcp serve` is stdio-only, needs a Cloudflare Tunnel or nginx shim to be reachable from Desktop.

The `claude-code-sessions` mirror below stays but is low-signal for Rob (barely uses Claude Code).

## One-way mirror (VPS receive-only)

For a folder the VPS should only READ, never write back into — e.g. mirroring Connie's Claude Code transcripts (`C:\Users\Rob\.claude\projects`) so the agent can read sessions without touching the source. The flow REVERSES: **Connie initiates the share** from her GUI (Add Folder → set label/ID/path → Sharing tab → check `robshermes`). The VPS then sees the offer:

```bash
curl -s -H "X-API-Key: $API" http://127.0.0.1:8384/rest/cluster/pending/folders
```

and accepts with the same POST `/rest/config/folders` call but `"type":"receiveonly"` and a landing path OUTSIDE the vault (raw transcripts are not curated notes), e.g. `/root/.hermes/mirrors/claude-code-sessions`. A digest/cron job reads from there; only distilled notes go in the vault.

## Verify sync is actually working

Don't trust the green GUI badges alone. Verify:

```bash
# On the VPS — drop a test file in the shared folder
echo "sync test $(date)" > /root/.hermes/vault/.syncthing-test.txt
```

```powershell
# On Connie — within ~10 seconds it should appear
Get-Content "C:\Users\Rob\Documents\Obsidian Vault\.syncthing-test.txt"
```

Then the reverse direction:

```powershell
# On Connie
"reverse test $(Get-Date)" | Out-File "C:\Users\Rob\Documents\Obsidian Vault\.syncthing-reverse.txt"
```

```bash
# On the VPS
cat /root/.hermes/vault/.syncthing-reverse.txt
```

Both directions confirmed → clean up the test files and you're done.

## Pitfalls

- **`syncthing` not recognized after winget install.** The installer modifies PATH but the current PowerShell window doesn't pick it up. Close + reopen PowerShell. If still not recognized, find the binary: `Get-ChildItem "$env:LOCALAPPDATA\Microsoft\WinGet\Packages" -Recurse -Filter syncthing.exe | Select-Object -First 1 FullName` and use the full path.
- **Config is at `~/.local/state/syncthing/`, not `~/.config/syncthing/`.** Debian's systemd unit uses the newer XDG state dir. If you `cat ~/.config/syncthing/config.xml` and get "No such file", that's why — you're looking at the old path.
- **Firewall prompt on Windows is not a risk.** Syncthing requires mutual device-ID pairing; the port being open doesn't expose anything. Allow both Private and Public.
- **Relay fallback is silent and slow.** If direct connection on port 22000 fails (NAT, firewall), Syncthing uses public relays. Sync still works but at a fraction of the speed. Check the GUI's "Listeners" line — you want `1/1` (direct) not `1/2` or relays listed. Fix is opening TCP 22000 inbound on the VPS side.
- **`.stfolder` marker must exist.** Syncthing creates a hidden `.stfolder` inside each synced folder. If the folder is on a removable drive or gets wiped, Syncthing pauses rather than re-syncing into an empty dir (safety feature). If sync stops after a disk event, check for the marker.
- **Don't sync the same folder with two different Syncthing instances.** Additional devices (the user's Surface tablet is already device #3, paired 2026-07-19 as `B2RA3UL`, vault path `C:\Users\rkbla\Documents\Obsidian Vault`) pair to the VPS the same way. Do NOT also pair Connie↔Surface directly unless you want a mesh; star topology (everything pairs to VPS) matches the single-source-of-truth architecture.
- **Sync stuck at 9x% on a tiny file after a remove/re-add of a folder share.** Index exchange deadlocked. Fix: `systemctl restart syncthing@root` on the VPS — the fresh handshake completes instantly. Don't rebuild the share.
- **The vault doubles as a file bridge to the Surface.** The Surface user (`rkbla`) has no quick clipboard share with Connie — he was email-relaying text to himself. Faster: write .ps1 scripts or drop files into the vault from the VPS side; they appear on the Surface in seconds and he runs them there. For output back: have him copy log files INTO the vault and read them from the VPS. Never give him long paste-commands for the Surface — and never dictate type-it-yourself commands either: the tablet keyboard mangles hand-typed PowerShell (autocorrect, dropped chars → `at line:1 char:1` errors). Script files via the vault are the only reliable delivery path.
- **Bridge-script gotcha (hit 2026-07-19): a .ps1 ending in `pause` can still auto-close via Explorer right-click → Run with PowerShell.** Don't rely on `pause` to keep output on screen. Have the script `Out-File` its results to a vault path instead, window closes harmlessly, and the agent reads the output file from the VPS side after sync. Zero user copying in either direction.
- **New device quick path (verified twice):** install via winget → fresh PS window → `syncthing` → GUI password → firewall allow → Actions → Show ID → add device via REST on VPS → add device ID to existing folder's devices array (GET `/rest/config/folders/<id>`, append `{"deviceID":"..."}` to `devices`, PUT back) → user approves both prompts on the new device → set local path → sync.
- **Agent-written vault files can land `-rw-------` (600) and stall visibility on the Windows side (hit twice 2026-08-08).** The agent's write layer creates files with owner-only perms; Syncthing reads them (runs as root) but the Windows replica / Obsidian / Cowork side can fail to materialize or read them. After any agent write into a synced folder, `chmod 644` the file before telling Rob it's there. If he reports "don't see it," this is the FIRST check — `ls -la` the VPS path before touching Syncthing config.
- **Verified end-to-end 2026-08-09:** VPS write → Syncthing → Connie's `C:\Users\Rob\Documents\Obsidian Vault` confirmed (playbook file arrived). Folder `hermes-vault` at `/root/.hermes/vault`, shared to Connie + Surface + robshermes; config at `/root/.local/state/syncthing/config.xml`. When Rob reports a sync gap, verify in this order: (1) file exists + perms on VPS, (2) folder shared to the right device in config, (3) have him check the Windows path — don't rebuild shares.
- **Obsidian + Syncthing conflict files are rare but real.** If both sides edit the same note within the sync window (~seconds), Syncthing writes a `*.sync-conflict-*.md` file alongside the original rather than picking a winner. This is correct behavior — don't "fix" it by deleting conflict files; show the user both and let them merge.
- **The GUI on the VPS is localhost-only by default. Don't expose it.** If remote GUI access is needed, SSH-tunnel: `ssh -L 8384:127.0.0.1:8384 root@2.25.172.164`, then browse to `http://127.0.0.1:8384` on the local machine. Do not change the GUI listen address to `0.0.0.0` — that's an unauthenticated admin panel on the public internet.
- **Systemd unit is `syncthing@root`, not `syncthing`.** The `@` is a template instance — you must specify the user. `systemctl enable --now syncthing` (no `@`) will fail or do the wrong thing.
- **Connie auto-start needs an ADMIN PowerShell.** The bare winget Syncthing build has no GUI "start on login" toggle. Use a Scheduled Task; `Register-ScheduledTask` returns `Access is denied` from a normal shell. Full block in `windows-to-vps-file-transfer` → `references/syncthing-sync.md`. Args `--no-console --no-browser` = hidden, no GUI pop at every login.
- **0 files / state=idle right after pairing a NEW share.** Don't panic and don't rebuild — the sender (Connie) hasn't announced its file list yet. Poke the SENDER to rescan (its GUI → folder → Rescan), not the receiver. A receive-only VPS side has nothing to announce. If still zero after a sender rescan, read the sender's actual folder config: `[xml]$c = Get-Content "$env:LOCALAPPDATA\Syncthing\config.xml"; $c.configuration.folder | % { $_.id + " | " + $_.path + " | " + $_.type }` — confirm the path is exactly the folder with the files (a wrong path = empty share = 0 files, not an error).
- **`127.0.0.1:8384` 404s/refuses on Connie** = the syncthing process died (window closed, sleep, reboot before auto-start existed). Check `Get-Process syncthing`, relaunch. Pairing + folder config persist in `%LOCALAPPDATA%\Syncthing`, restart resumes cleanly. Not a sync break.
- **Agent's own `nous_auth.json` gets synced to paired devices (hit 2026-07-26, broadcast for an unknown period before the user noticed).** The agent writes its OAuth refresh token to `<hermes-root>/shared/nous_auth.json` as a default — and the default `hermes-shared` Syncthing folder lives at exactly that path. This is a footgun by design collision: the agent's "share auth across profiles" feature and the Syncthing "share files across devices" feature were never meant to coexist, but they do on this VPS because both default to `<hermes-root>/shared/`. The `.stignore` rules alone don't prevent leakage — they only stop new scans, so anything already indexed keeps syncing. The full fix (env var redirect + move existing file + rescan to propagate deletion to paired devices) is in `references/nous-auth-store-redirect.md`. Whenever you set up or audit a shared folder on a system that runs the agent, redirect the auth store FIRST.

## Related

- `windows-to-vps-file-transfer` — one-shot pushes (scp, WinSCP, hPanel tarball). Use that for ad-hoc transfers; use this skill for ongoing sync.
- `verifying-user-claims` — the verification protocol applies here too: after pairing, verify with actual file drops in both directions, not just GUI badges.
- `operating-hermes-gateway` — the broader VPS-as-source-of-truth architecture that motivates putting the vault on the VPS in the first place.
