Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions GhosttyTabs.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
AGSV0003A1B2C3D4E5F60719 /* TerminalController+AgentSupervision.swift in Sources */ = {isa = PBXBuildFile; fileRef = AGSV0004A1B2C3D4E5F60719 /* TerminalController+AgentSupervision.swift */; };
AGRP0001A1B2C3D4E5F60719 /* AgentRPCDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = AGRP0002A1B2C3D4E5F60719 /* AgentRPCDispatcher.swift */; };
AGSV0005A1B2C3D4E5F60719 /* AgentSupervisionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AGSV0006A1B2C3D4E5F60719 /* AgentSupervisionTests.swift */; };
LPRT0001A1B2C3D4E5F60719 /* LeftoverProcessReaperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = LPRT0002A1B2C3D4E5F60719 /* LeftoverProcessReaperTests.swift */; };
AGOV0001A1B2C3D4E5F60719 /* AgentHelpersPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = AGOV0002A1B2C3D4E5F60719 /* AgentHelpersPopover.swift */; };
AGOV0003A1B2C3D4E5F60719 /* AgentHelpersPopoverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AGOV0004A1B2C3D4E5F60719 /* AgentHelpersPopoverTests.swift */; };
AMDT000001 /* AgentManifest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AMDT000002 /* AgentManifest.swift */; };
Expand Down Expand Up @@ -488,6 +489,7 @@
AGSV0004A1B2C3D4E5F60719 /* TerminalController+AgentSupervision.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TerminalController+AgentSupervision.swift"; sourceTree = "<group>"; };
AGRP0002A1B2C3D4E5F60719 /* AgentRPCDispatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentRPCDispatcher.swift; sourceTree = "<group>"; };
AGSV0006A1B2C3D4E5F60719 /* AgentSupervisionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentSupervisionTests.swift; sourceTree = "<group>"; };
LPRT0002A1B2C3D4E5F60719 /* LeftoverProcessReaperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeftoverProcessReaperTests.swift; sourceTree = "<group>"; };
AGOV0002A1B2C3D4E5F60719 /* AgentHelpersPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentHelpersPopover.swift; sourceTree = "<group>"; };
AGOV0004A1B2C3D4E5F60719 /* AgentHelpersPopoverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentHelpersPopoverTests.swift; sourceTree = "<group>"; };
AMDT000002 /* AgentManifest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentManifest.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1350,6 +1352,7 @@
BC39DE4B96D1931C52AF7D68 /* SidebarOrderingTests.swift */,
D1A0B0C1A1B2C3D4E5F60719 /* SidebarMarkdownRendererTests.swift */,
AGSV0006A1B2C3D4E5F60719 /* AgentSupervisionTests.swift */,
LPRT0002A1B2C3D4E5F60719 /* LeftoverProcessReaperTests.swift */,
AGOV0004A1B2C3D4E5F60719 /* AgentHelpersPopoverTests.swift */,
B09C007F42697761B5F1A2AB /* OmnibarAndToolsTests.swift */,
D2C075029771815DD5DA1332 /* NotificationAndMenuBarTests.swift */,
Expand Down Expand Up @@ -1834,6 +1837,7 @@
CB23911D7E131E8FBC9B82B6 /* SidebarOrderingTests.swift in Sources */,
D1A0B0C0A1B2C3D4E5F60719 /* SidebarMarkdownRendererTests.swift in Sources */,
AGSV0005A1B2C3D4E5F60719 /* AgentSupervisionTests.swift in Sources */,
LPRT0001A1B2C3D4E5F60719 /* LeftoverProcessReaperTests.swift in Sources */,
AGOV0003A1B2C3D4E5F60719 /* AgentHelpersPopoverTests.swift in Sources */,
4378399A7C0245EF8186F306 /* OmnibarAndToolsTests.swift in Sources */,
734F49D37E543DD01C2F4FEF /* NotificationAndMenuBarTests.swift in Sources */,
Expand Down
51 changes: 51 additions & 0 deletions Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -10090,6 +10090,57 @@
}
}
},
"settings.app.reapLeftoverAgentProcesses": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Stop Leftover Agent Processes"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "残ったエージェントのプロセスを停止"
}
}
}
},
"settings.app.reapLeftoverAgentProcesses.subtitleOff": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Processes Claude Code started keep running after it exits."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "Claude Code が起動したプロセスは、終了後も実行され続けます。"
}
}
}
},
"settings.app.reapLeftoverAgentProcesses.subtitleOn": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "When Claude Code exits, processes it started and left running are stopped. Processes started with nohup are kept."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "Claude Code の終了時に、起動したまま残っているプロセスを停止します。nohup で起動したプロセスは残ります。"
}
}
}
},
"settings.app.settingsFile.openButton": {
"extractionState": "manual",
"localizations": {
Expand Down
5 changes: 5 additions & 0 deletions Resources/settings.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@
"default": true,
"description": "Resume Claude Code, Codex and OpenCode sessions that were running at quit when their pane is restored."
},
"reapLeftoverAgentProcesses": {
"type": "boolean",
"default": true,
"description": "When a Claude Code session exits, stop the processes it started and left running (MCP servers, dev servers, debug binaries). Processes started with nohup are kept."
},
"commandPaletteSearchesAllSurfaces": {
"type": "boolean",
"default": false,
Expand Down
26 changes: 22 additions & 4 deletions Sources/AgentSupervision.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Bonsplit
import Combine
import CryptoKit
import Foundation
Expand Down Expand Up @@ -303,7 +304,7 @@ final class AgentSupervisionRegistry: ObservableObject {
let ids = recordsById.values.compactMap { record in
record.host == host && record.session == session && !record.state.isFinished ? record.id : nil
}
return ids.compactMap { try? finish(id: $0, state: state, now: now) }
return ids.compactMap { id in logFailure("finish", id: id) { try finish(id: id, state: state, now: now) } }
}

func finishActiveSurface(
Expand All @@ -316,7 +317,7 @@ final class AgentSupervisionRegistry: ObservableObject {
throw AgentSupervisionRegistryError.finishStateRequired
}
let ids = activeSurfaceRecordIds(workspaceId: workspaceId, surfaceId: surfaceId)
return ids.compactMap { try? finish(id: $0, state: state, now: now) }
return ids.compactMap { id in logFailure("finish", id: id) { try finish(id: id, state: state, now: now) } }
}

func updateActiveSurface(
Expand All @@ -329,7 +330,7 @@ final class AgentSupervisionRegistry: ObservableObject {
throw AgentSupervisionRegistryError.finishStateRequired
}
let ids = activeSurfaceRecordIds(workspaceId: workspaceId, surfaceId: surfaceId)
return ids.compactMap { try? update(id: $0, state: state, now: now) }
return ids.compactMap { id in logFailure("update", id: id) { try update(id: id, state: state, now: now) } }
}

/// Finishes the workspace's in-process helpers whose parent agent exited without a
Expand All @@ -349,7 +350,24 @@ final class AgentSupervisionRegistry: ObservableObject {
? record.id
: nil
}
return ids.compactMap { try? finish(id: $0, state: state, now: now) }
return ids.compactMap { id in logFailure("finish", id: id) { try finish(id: id, state: state, now: now) } }
}

/// Runs one step of a multi-record loop: a failure is logged and skipped so the rest still run.
private func logFailure(
_ operation: String,
id: UUID,
_ body: () throws -> AgentTaskRecord
) -> AgentTaskRecord? {
do {
return try body()
} catch {
#if DEBUG
dlog("agent.supervision \(operation) id=\(id.uuidString.prefix(8)) error=\(error)")
#endif
dilog("agent", "supervision \(operation) id=\(id.uuidString.prefix(8)) error=\(error)")
return nil
}
}

func removeAll() {
Expand Down
Loading
Loading