Commit 1cf02268b941

Vincent Demeester <vincent@sbr.pm>
2026-02-27 11:45:37
fix: resolve syncthing conflicts in AI logs
Added hostname suffix to session-log filenames to prevent concurrent-append conflicts across machines. Removed the claude/history symlink to claude-sync so tool-outputs stay machine-local (2.4GB of ephemeral debug data). Updated the /session-log command to glob all per-host log files. Added tsconfig and @types/node for Claude hooks LSP support.
1 parent ae39499
Changed files (6)
dots
config
pi
agent
extensions
ai-storage
home
common
dots/config/claude/hooks/bun.lock
@@ -0,0 +1,17 @@
+{
+  "lockfileVersion": 1,
+  "configVersion": 1,
+  "workspaces": {
+    "": {
+      "name": "claude-hooks",
+      "devDependencies": {
+        "@types/node": "^25.3.2",
+      },
+    },
+  },
+  "packages": {
+    "@types/node": ["@types/node@25.3.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q=="],
+
+    "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
+  }
+}
dots/config/claude/hooks/lib.ts
@@ -106,7 +106,7 @@ export function appendLog(file: string, line: string): void {
 }
 
 export function sessionLogPath(d: Date = now()): string {
-  return join(SESSIONS_DIR, yearMonth(d), `${dateStr(d)}_session-log.txt`);
+  return join(SESSIONS_DIR, yearMonth(d), `${dateStr(d)}_session-log_${host()}.txt`);
 }
 
 // ── Read stdin (for hooks that receive JSON) ───────────────────────
dots/config/claude/hooks/package.json
@@ -2,5 +2,8 @@
   "name": "claude-hooks",
   "version": "1.0.0",
   "type": "module",
-  "description": "TypeScript hooks for Claude Code — unified AI storage"
+  "description": "TypeScript hooks for Claude Code — unified AI storage",
+  "devDependencies": {
+    "@types/node": "^25.3.2"
+  }
 }
dots/config/claude/hooks/tsconfig.json
@@ -0,0 +1,12 @@
+{
+  "compilerOptions": {
+    "target": "ESNext",
+    "module": "ESNext",
+    "moduleResolution": "bundler",
+    "types": ["node"],
+    "strict": true,
+    "skipLibCheck": true,
+    "noEmit": true
+  },
+  "include": ["*.ts"]
+}
dots/pi/agent/extensions/ai-storage/index.ts
@@ -110,11 +110,17 @@ export default function (pi: ExtensionAPI) {
 		};
 	}
 
+	/** Session-log filename includes hostname to avoid syncthing conflicts
+	 *  when multiple machines append to the same daily log file. */
+	function sessionLogFile(sessionDir: string, date: string): string {
+		return join(sessionDir, `${date}_session-log_${hostname()}.txt`);
+	}
+
 	async function logSessionStart() {
 		const tool = detectTool();
 		const { yearMonth, date, timestamp } = getDateInfo();
 		const sessionDir = join(SESSIONS_DIR, yearMonth);
-		const logFile = join(sessionDir, `${date}_session-log.txt`);
+		const logFile = sessionLogFile(sessionDir, date);
 
 		await mkdir(sessionDir, { recursive: true });
 		await appendFile(logFile, `${timestamp} - Session started (${tool})\n`);
@@ -123,7 +129,7 @@ export default function (pi: ExtensionAPI) {
 	async function appendToSessionLog(message: string) {
 		const { yearMonth, date, timestamp } = getDateInfo();
 		const sessionDir = join(SESSIONS_DIR, yearMonth);
-		const logFile = join(sessionDir, `${date}_session-log.txt`);
+		const logFile = sessionLogFile(sessionDir, date);
 
 		await mkdir(sessionDir, { recursive: true });
 		await appendFile(logFile, `${timestamp} - ${message}\n`);
@@ -1092,15 +1098,27 @@ After generating the summary, use the save_session_to_history tool to save it${c
 		description: "View today's session log",
 		handler: async (_args, ctx) => {
 			const { yearMonth, date } = getDateInfo();
-			const logFile = join(SESSIONS_DIR, yearMonth, `${date}_session-log.txt`);
+			const sessionDir = join(SESSIONS_DIR, yearMonth);
 
-			if (!existsSync(logFile)) {
+			// Read all session-log files for today (one per hostname)
+			let allContent = "";
+			if (existsSync(sessionDir)) {
+				const files = await readdir(sessionDir);
+				const logFiles = files.filter((f) => f.startsWith(`${date}_session-log`) && f.endsWith(".txt"));
+				for (const f of logFiles) {
+					try {
+						allContent += await readFile(join(sessionDir, f), "utf-8");
+					} catch {}
+				}
+			}
+
+			if (!allContent.trim()) {
 				ctx.ui.notify("No session log for today", "info");
 				return;
 			}
 
 			try {
-				const content = await readFile(logFile, "utf-8");
+				const content = allContent;
 				const lines = content.trim().split("\n").reverse(); // Most recent first
 				const theme = ctx.ui.theme;
 
home/common/dev/ai.nix
@@ -12,8 +12,8 @@ let
 in
 {
   # Ensure claude-sync directory structure exists (legacy, still used by claude)
+  # NOTE: claude-sync/history is no longer synced — tool-outputs stay local
   xdg.dataFile = {
-    "claude-sync/history/.keep".text = "";
     "claude-sync/projects/.keep".text = "";
     "claude-sync/todos/.keep".text = "";
     "claude-sync/plans/.keep".text = "";
@@ -58,11 +58,9 @@ in
 
   # Symlink claude directories to synced location
   # force = true because claude-code may recreate these dirs during operation
+  # NOTE: claude/history is intentionally NOT symlinked — tool-outputs are
+  # machine-local (large, ephemeral debug data that causes syncthing conflicts).
   xdg.configFile = {
-    "claude/history" = {
-      source = config.lib.file.mkOutOfStoreSymlink "${claudeSyncDir}/history";
-      force = true;
-    };
     "claude/projects" = {
       source = config.lib.file.mkOutOfStoreSymlink "${claudeSyncDir}/projects";
       force = true;