Skip to content

Commit cf74818

Browse files
committed
Fix v1.3 hook migration, stale sessions, and MCP tool names
1 parent 27a7ac5 commit cf74818

4 files changed

Lines changed: 50 additions & 43 deletions

File tree

ClaudeIsland/Core/ClaudePaths.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,11 @@ enum ClaudePaths {
6262
claudeDir.appendingPathComponent("projects")
6363
}
6464

65-
/// Shell-expanded path for hook commands in settings.json.
66-
/// Uses "~/" so Claude Code resolves it relative to the user's home.
65+
/// Shell-safe absolute path for hook commands in settings.json.
66+
/// Absolute paths keep custom directories and ~/.config/claude working;
67+
/// quoting keeps paths with spaces from being split by the shell.
6768
static var hookScriptShellPath: String {
68-
let dirName = claudeDir.lastPathComponent
69-
return "~/\(dirName)/hooks/claude-island-state.py"
69+
shellQuote(claudeDir.appendingPathComponent("hooks/claude-island-state.py").path)
7070
}
7171

7272
/// Invalidate the cached directory so the next access re-resolves.
@@ -110,4 +110,8 @@ enum ClaudePaths {
110110
// 4. Legacy fallback
111111
return home.appendingPathComponent(".claude")
112112
}
113+
114+
private static func shellQuote(_ path: String) -> String {
115+
"'" + path.replacingOccurrences(of: "'", with: "'\\''") + "'"
116+
}
113117
}

ClaudeIsland/Services/Hooks/HookInstaller.swift

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -79,23 +79,9 @@ struct HookInstaller {
7979
]
8080

8181
for (event, config) in hookEvents {
82-
if var existingEvent = hooks[event] as? [[String: Any]] {
83-
let hasOurHook = existingEvent.contains { entry in
84-
if let entryHooks = entry["hooks"] as? [[String: Any]] {
85-
return entryHooks.contains { h in
86-
let cmd = h["command"] as? String ?? ""
87-
return cmd.contains("claude-island-state.py")
88-
}
89-
}
90-
return false
91-
}
92-
if !hasOurHook {
93-
existingEvent.append(contentsOf: config)
94-
hooks[event] = existingEvent
95-
}
96-
} else {
97-
hooks[event] = config
98-
}
82+
let existingEvent = hooks[event] as? [[String: Any]] ?? []
83+
let cleanedEvent = existingEvent.compactMap { removingClaudeIslandHooks(from: $0) }
84+
hooks[event] = cleanedEvent + config
9985
}
10086

10187
json["hooks"] = hooks
@@ -151,15 +137,7 @@ struct HookInstaller {
151137

152138
for (event, value) in hooks {
153139
if var entries = value as? [[String: Any]] {
154-
entries.removeAll { entry in
155-
if let entryHooks = entry["hooks"] as? [[String: Any]] {
156-
return entryHooks.contains { hook in
157-
let cmd = hook["command"] as? String ?? ""
158-
return cmd.contains("claude-island-state.py")
159-
}
160-
}
161-
return false
162-
}
140+
entries = entries.compactMap { removingClaudeIslandHooks(from: $0) }
163141

164142
if entries.isEmpty {
165143
hooks.removeValue(forKey: event)
@@ -200,4 +178,22 @@ struct HookInstaller {
200178

201179
return "python"
202180
}
181+
182+
nonisolated private static func removingClaudeIslandHooks(from entry: [String: Any]) -> [String: Any]? {
183+
guard var entryHooks = entry["hooks"] as? [[String: Any]] else {
184+
return entry
185+
}
186+
187+
entryHooks.removeAll(where: isClaudeIslandHook)
188+
guard !entryHooks.isEmpty else { return nil }
189+
190+
var updatedEntry = entry
191+
updatedEntry["hooks"] = entryHooks
192+
return updatedEntry
193+
}
194+
195+
nonisolated private static func isClaudeIslandHook(_ hook: [String: Any]) -> Bool {
196+
let cmd = hook["command"] as? String ?? ""
197+
return cmd.contains("claude-island-state.py")
198+
}
203199
}

ClaudeIsland/Services/Session/ConversationParser.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -657,12 +657,12 @@ actor ConversationParser {
657657
isError: Bool
658658
) -> ToolResultData {
659659
if toolName.hasPrefix("mcp__") {
660-
let parts = toolName.dropFirst(5).split(separator: "_", maxSplits: 2)
661-
let serverName = parts.count > 0 ? String(parts[0]) : "unknown"
662-
let mcpToolName = parts.count > 1 ? String(parts[1].dropFirst()) : toolName
660+
let parts = String(toolName.dropFirst(5)).components(separatedBy: "__")
661+
let serverName = parts.first.flatMap { $0.isEmpty ? nil : $0 } ?? "unknown"
662+
let mcpToolName = parts.dropFirst().joined(separator: "__")
663663
return .mcp(MCPResult(
664664
serverName: serverName,
665-
toolName: mcpToolName,
665+
toolName: mcpToolName.isEmpty ? toolName : mcpToolName,
666666
rawResult: toolUseResult
667667
))
668668
}
@@ -1131,4 +1131,3 @@ extension ConversationParser {
11311131
return tools
11321132
}
11331133
}
1134-

ClaudeIsland/Services/State/SessionStore.swift

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,19 +1070,23 @@ actor SessionStore {
10701070

10711071
/// Recheck status of all active sessions
10721072
private func recheckAllSessions() {
1073-
for (sessionId, session) in sessions {
1074-
guard session.phase != .ended else { continue }
1073+
var removedSession = false
1074+
1075+
for (sessionId, session) in Array(sessions) {
1076+
if session.phase == .ended {
1077+
sessions.removeValue(forKey: sessionId)
1078+
cancelPendingSync(sessionId: sessionId)
1079+
removedSession = true
1080+
continue
1081+
}
10751082

10761083
if let pid = session.pid {
10771084
let isRunning = isProcessRunning(pid: pid)
10781085
if !isRunning {
10791086
Self.logger.info("Process \(pid) no longer running, ending session \(sessionId.prefix(8))")
1080-
var updatedSession = session
1081-
if updatedSession.phase.canTransition(to: .ended) {
1082-
updatedSession.phase = .ended
1083-
sessions[sessionId] = updatedSession
1084-
publishState()
1085-
}
1087+
sessions.removeValue(forKey: sessionId)
1088+
cancelPendingSync(sessionId: sessionId)
1089+
removedSession = true
10861090
continue
10871091
}
10881092
}
@@ -1098,6 +1102,10 @@ actor SessionStore {
10981102
scheduleFileSync(sessionId: sessionId, cwd: session.cwd)
10991103
}
11001104
}
1105+
1106+
if removedSession {
1107+
publishState()
1108+
}
11011109
}
11021110

11031111
/// Check if a process is still running

0 commit comments

Comments
 (0)