Skip to content
Closed
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: 2 additions & 2 deletions CLI/cmux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24320,7 +24320,7 @@ struct CMUXCLI {
"command": command,
"auto_resume": true
]
if let cwd = normalizedHookValue(cwd) ?? normalizedHookValue(launchCommand?.workingDirectory) {
if let cwd = normalizedHookValue(launchCommand?.workingDirectory) ?? normalizedHookValue(cwd) {
params["cwd"] = cwd
}
if let resumeEnvironment, !resumeEnvironment.isEmpty {
Expand Down Expand Up @@ -24402,7 +24402,7 @@ struct CMUXCLI {
guard let argv, !argv.isEmpty else { return nil }
return agentSurfaceResumeShellCommand(
argv: argv,
workingDirectory: workingDirectory ?? launchCommand?.workingDirectory,
workingDirectory: launchCommand?.workingDirectory ?? workingDirectory,
kind: kind,
environment: environment
)
Expand Down
7 changes: 5 additions & 2 deletions Sources/RestorableAgentSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ enum AgentResumeCommandBuilder {

let cwd = !includeWorkingDirectoryPrefix || customRegistration?.cwd == .ignore
? nil
: normalized(workingDirectory ?? launchCommand?.workingDirectory)
: normalized(launchCommand?.workingDirectory ?? workingDirectory)
let sanitizedCommandParts = customRegistration == nil
? AgentLaunchSanitizer.removingSavedWorkingDirectoryOptions(
from: commandParts,
Expand Down Expand Up @@ -666,11 +666,14 @@ enum AgentResumeCommandBuilder {
let sessionDirectory = normalized(registration.sessionDirectory).map {
($0 as NSString).expandingTildeInPath
}
let resolvedCwd = registration.cwd == .ignore
? nil
: normalized(launchCommand?.workingDirectory ?? workingDirectory)
Comment on lines +669 to +671

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Don't blank {{cwd}} when only cd-prefix is ignored

For custom Vault agents, cwd is documented as controlling whether Vault should cd before running resumeCommand, not whether the {{cwd}} template value is available. With cwd: .ignore and a template like tool --resume {{sessionId}} --dir {{cwd}}, this makes the replacement empty; the later empty-token guard returns [], so snapshot.resumeCommand becomes nil. Agents that must avoid a shell cd but still need the saved project path as an argv flag can no longer resume; keep suppressing the prefix in shellCommand but continue filling {{cwd}} from the launch/working directory.

Useful? React with 👍 / 👎.

Comment thread
TravelThePath marked this conversation as resolved.
let replacements: [String: String] = [
"sessionId": sessionId,
"sessionPath": sessionId,
"executable": original.executable,
"cwd": normalized(workingDirectory ?? launchCommand?.workingDirectory) ?? "",
"cwd": resolvedCwd ?? "",
"sessionDir": sessionDirectory ?? "",
]
var resolved: [String] = []
Expand Down
88 changes: 88 additions & 0 deletions cmuxTests/RestorableAgentHookProviderResumeTests.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import CMUXAgentLaunch
import Testing
import XCTest

#if canImport(cmux_DEV)
Expand Down Expand Up @@ -641,3 +642,90 @@ extension SocketListenerAcceptPolicyTests {
)
}
}

@Suite
struct RestorableAgentResumeWorkingDirectoryTests {
@Test
func claudeResumeCommandChangesToLaunchDirectoryNotDriftedCwd() {
let snapshot = SessionRestorableAgentSnapshot(
kind: .claude,
sessionId: "59729506-e83c-4bfe-a730-1d50d10cc396",
workingDirectory: "/tmp/claude repo/nested",
launchCommand: AgentLaunchCommandSnapshot(
launcher: "claude",
executablePath: "/Users/example/.local/bin/claude",
arguments: ["/Users/example/.local/bin/claude"],
workingDirectory: "/tmp/claude repo",
environment: nil,
capturedAt: 123,
source: "process"
)
)

#expect(
snapshot.resumeCommand
== "{ cd -- '/tmp/claude repo' 2>/dev/null || [ ! -d '/tmp/claude repo' ]; } && '/Users/example/.local/bin/claude' '--resume' '59729506-e83c-4bfe-a730-1d50d10cc396'"
)
}

@Test
func customVaultResumeTemplateUsesLaunchDirectoryNotDriftedCwd() throws {
let snapshot = SessionRestorableAgentSnapshot(
kind: .custom("vault-tool"),
sessionId: "session-123",
workingDirectory: "/tmp/vault-drifted",
launchCommand: AgentLaunchCommandSnapshot(
launcher: "vault-tool",
executablePath: "/usr/local/bin/vault-tool",
arguments: ["/usr/local/bin/vault-tool"],
workingDirectory: "/tmp/vault-launch",
environment: nil,
capturedAt: 123,
source: "process"
),
registration: CmuxVaultAgentRegistration(
id: "vault-tool",
name: "Vault Tool",
detect: CmuxVaultAgentDetectRule(processName: "vault-tool"),
sessionIdSource: .argvOption("--resume"),
resumeCommand: "{{executable}} --resume {{sessionId}} --dir {{cwd}}",
cwd: .preserve
)
)

let command = try #require(snapshot.resumeCommand)
#expect(command.contains("/tmp/vault-launch"))
#expect(!command.contains("/tmp/vault-drifted"))
}

@Test
func customVaultResumeTemplateSuppressesCwdWhenIgnored() {
let snapshot = SessionRestorableAgentSnapshot(
kind: .custom("vault-tool"),
sessionId: "session-123",
workingDirectory: "/tmp/vault-drifted",
launchCommand: AgentLaunchCommandSnapshot(
launcher: "vault-tool",
executablePath: "/usr/local/bin/vault-tool",
arguments: ["/usr/local/bin/vault-tool"],
workingDirectory: "/tmp/vault-launch",
environment: nil,
capturedAt: 123,
source: "process"
),
registration: CmuxVaultAgentRegistration(
id: "vault-tool",
name: "Vault Tool",
detect: CmuxVaultAgentDetectRule(processName: "vault-tool"),
sessionIdSource: .argvOption("--resume"),
resumeCommand: "{{executable}} --resume {{sessionId}} --dir {{cwd}}",
cwd: .ignore
)
)

// cwd: .ignore must keep the launch dir out of the resolved command entirely.
let command = snapshot.resumeCommand ?? ""
#expect(!command.contains("/tmp/vault-launch"))
#expect(!command.contains("/tmp/vault-drifted"))
Comment thread
TravelThePath marked this conversation as resolved.
}
Comment thread
TravelThePath marked this conversation as resolved.
}