Syncing VS Code Editor to Obsidian on macOS

As I have begun digging into the world of AI-assisted editing for both code and non-code files, I have been frustrated by the inability of ChatGPT to interact directly with files that are open in Obsidian the way it can through VS Code. Since I usually have both Obsidian and VS Code open at the same time, this problem could be solved by simply making sure that Obsidian and VS Code are looking at the same file at the same time.

This was also a good exercise in accomplishing specific real-world results with the help of ChatGPT.

🧩 Components Used

The setup involved three main steps:

  1. Obsidian Current File Plugin

    We installed the Current File community plugin, which writes the path of the currently open file in Obsidian to a fixed location on disk. This gave us a simple and reliable way to monitor which file is currently active.

  2. Shell Script Using fswatch

    We wrote a lightweight shell script that uses fswatch to monitor changes to the current file output. When it detects that Obsidian has focused a new file, it:

    • Checks if that file exists

    • Switches the currently open tab in VS Code (if applicable) to match the new file

    • Falls back gracefully if VS Code isn’t open or the file is missing

#!/bin/bash

# Ensure your script has access to fswatch and jq
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"

# Set up error logging
exec >> /tmp/watchobsidian.debug.log 2>&1

echo "watch-obsidian-selection.sh started from $0 at $(date)"

# CONFIGURATION

WATCH_FILE="$HOME/<path>/<to-obsidian-vault>/.obsidian/current-file.json"

# Check dependencies

command -v jq >/dev/null 2>&1 || { echo >&2 "jq is required. Install with: brew install jq"; exit 1; }

command -v fswatch >/dev/null 2>&1 || { echo >&2 "fswatch is required. Install with: brew install fswatch"; exit 1; }
  
# Note: jq (via Homebrew) may need to be granted access under:

# System Settings → Privacy & Security → Files and Folders → jq → Removable Volumes

# Full Disk Access is not required unless you're watching protected locations (e.g. ~/Library).

# Variable to limit fswatch
DEBOUNCE_SECONDS=1.0
LAST_RUN=0

echo "Watching file: $WATCH_FILE"

[ -f "$WATCH_FILE" ] || { echo "ERROR: Watch file does not exist: $WATCH_FILE"; exit 1; }
  
echo "Starting fswatch loop..."

fswatch -0 "$WATCH_FILE" | while IFS= read -r -d "" event; do

NOW=$(date +%s.%N)
TIME_DIFF=$(echo "$NOW - $LAST_RUN" | bc)

# Debounce to avoid rapid repeat triggers

if (( $(echo "$TIME_DIFF < $DEBOUNCE_SECONDS" | bc -l) )); then

continue

fi

LAST_RUN=$NOW

FILE_PATH=$(jq -r '.fullpath' "$WATCH_FILE")

# Check if VS Code is already running
if ! pgrep -f "/Applications/Visual Studio Code.app/Contents/MacOS/Electron" > /dev/null; then

echo "VS Code not running. Skipping open."

continue

fi

# If VS Code is open, open the current Obsidian file
if [[ -f "$FILE_PATH" ]]; then
echo "Opening in VS Code (background): $FILE_PATH"
open -g -a "Visual Studio Code" "$FILE_PATH"

else

echo "File does not exist: $FILE_PATH"

fi

done

echo "Exited fswatch block"
  1. Adding the Script to launchd

    Finally, we set the script to run in the background using launchd, so the synchronization starts automatically at login and continues invisibly. This makes the entire setup frictionless — once configured, there’s no interaction required. Save the following XML content as a file named: ~/Library/LaunchAgents/com.<username>.watchobsidian.plist

<!-- launchd configuration file -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.<username>.watchobsidian</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Users/<username>/path/to/watch-obsidian-selection.sh</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/tmp/watchobsidian.launchd.log</string>
    <key>StandardErrorPath</key>
    <string>/tmp/watchobsidian.launchd.err</string>
</dict>
</plist>

🧠 Why Bother?

This setup eliminates the need to manually switch files in VS Code while navigating in Obsidian, and means when I switch from Obsidian to ChatGPT app, the file selected in VS Code (and therefore connected to ChatGPT) remains in sync. It’s a quality-of-life upgrade that supports tighter integration between the ChatGPT app and Obsidian and was a useful experiment in automating with Obsidian. At some point I might convert it to a stand-alone Obsidian extension as an exercise.