Skip to content

Commit f9e648e

Browse files
gi11esclaude
andauthored
fix: resolve symlinks when looking up Claude session files (#51)
* fix: resolve symlinks when looking up Claude session files When a project is added via a symlink path (e.g. ~/obsidian pointing to an iCloud Drive vault), Deckard stores the literal symlink path while Claude Code CLI resolves symlinks before creating session directories. This path mismatch causes ContextMonitor and QuotaMonitor to miss the JSONL session files, so model/context info is absent from the sidebar. Resolve symlinks using NSString.resolvingSymlinksInPath at the three lookup sites before encoding the project path to a directory name. Fixes #45 Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: extract shared helper and fix missed session-resume path Add String.claudeProjectDirName that resolves symlinks before encoding the project path, replacing the inline resolve+encode at all 4 call sites. Also fixes the missed fourth site in DeckardWindowController where session resume would silently fail for symlinked projects. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
1 parent 8c50e6a commit f9e648e

File tree

3 files changed

+12
-4
lines changed

3 files changed

+12
-4
lines changed

Sources/Detection/ContextMonitor.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import Foundation
22

3+
extension String {
4+
/// Encodes a project path into the directory name Claude Code uses under `~/.claude/projects/`.
5+
/// Resolves symlinks first so the encoded name matches the canonical path the CLI uses.
6+
var claudeProjectDirName: String {
7+
(self as NSString).resolvingSymlinksInPath.replacingOccurrences(of: "/", with: "-")
8+
}
9+
}
10+
311
/// Reads Claude Code session JSONL files to calculate context usage.
412
class ContextMonitor {
513
static let shared = ContextMonitor()
@@ -34,7 +42,7 @@ class ContextMonitor {
3442

3543
/// Lists all Claude sessions for a project, sorted by most recent first.
3644
func listSessions(forProjectPath projectPath: String) -> [SessionInfo] {
37-
let encoded = projectPath.replacingOccurrences(of: "/", with: "-")
45+
let encoded = projectPath.claudeProjectDirName
3846
let dir = NSHomeDirectory() + "/.claude/projects/\(encoded)"
3947
let fm = FileManager.default
4048

@@ -93,7 +101,7 @@ class ContextMonitor {
93101
/// Get context usage for a session by reading its JSONL file.
94102
/// Only reads the tail of the file to find the most recent usage entry.
95103
func getUsage(sessionId: String, projectPath: String) -> ContextUsage? {
96-
let encoded = projectPath.replacingOccurrences(of: "/", with: "-")
104+
let encoded = projectPath.claudeProjectDirName
97105
let jsonlPath = NSHomeDirectory() + "/.claude/projects/\(encoded)/\(sessionId).jsonl"
98106

99107
guard let fh = FileHandle(forReadingAtPath: jsonlPath) else { return nil }

Sources/Detection/QuotaMonitor.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ class QuotaMonitor {
112112
var bestDate: Date = .distantPast
113113

114114
for projectPath in projectPaths {
115-
let encoded = projectPath.replacingOccurrences(of: "/", with: "-")
115+
let encoded = projectPath.claudeProjectDirName
116116
let dir = NSHomeDirectory() + "/.claude/projects/\(encoded)"
117117

118118
guard let files = try? fm.contentsOfDirectory(atPath: dir) else { continue }

Sources/Window/DeckardWindowController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ class DeckardWindowController: NSWindowController, NSSplitViewDelegate {
618618
let extraArgsSuffix = extraArgs.isEmpty ? "" : " \(extraArgs)"
619619
var claudeArgs = extraArgsSuffix
620620
if let sessionIdToResume {
621-
let encoded = project.path.replacingOccurrences(of: "/", with: "-")
621+
let encoded = project.path.claudeProjectDirName
622622
let jsonlPath = NSHomeDirectory() + "/.claude/projects/\(encoded)/\(sessionIdToResume).jsonl"
623623
if FileManager.default.fileExists(atPath: jsonlPath) {
624624
claudeArgs = " --resume \(sessionIdToResume)\(extraArgsSuffix)"

0 commit comments

Comments
 (0)