fix: drop /root/repo fallback in vm exec for unbound VMs
vm exec defaulted execGuestPath to /root/repo whenever the VM had no recorded workspace, so running it against a plain VM (one that never had vm workspace prepare / vm run ./repo) blew up with 'cd: /root/repo: No such file or directory' — surfaced via the login shell's mise activate hook because bash -lc sources profile.d before the explicit cd. Now auto-cd only fires when --guest-path is passed or the VM actually has a workspace recorded; otherwise the command runs from root's home. Mise wrapping unchanged — without a .mise.toml it's a no-op. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9400bab6fd
commit
b0a9d64f4a
3 changed files with 81 additions and 18 deletions
|
|
@ -21,13 +21,14 @@ func (d *deps) newVMExecCommand() *cobra.Command {
|
|||
Use: "exec <id-or-name> -- <command> [args...]",
|
||||
Short: "Run a command in the VM workspace with the repo toolchain",
|
||||
Long: strings.TrimSpace(`
|
||||
Run a command inside a persistent VM, automatically cd-ing into the
|
||||
prepared workspace and wrapping the command with 'mise exec' so all
|
||||
mise-managed tools (Go, Node, Python, etc.) are on PATH.
|
||||
Run a command inside a persistent VM, wrapping it with 'mise exec' so
|
||||
all mise-managed tools (Go, Node, Python, etc.) are on PATH.
|
||||
|
||||
The workspace path comes from the last 'vm workspace prepare' or
|
||||
'vm run ./repo' on this VM. If the host repo has advanced since then,
|
||||
banger warns; pass --auto-prepare to re-sync the workspace first.
|
||||
If the VM has a prepared workspace (from 'vm workspace prepare' or
|
||||
'vm run ./repo'), the command runs from that directory and a stale-
|
||||
workspace warning is printed when the host repo has advanced since the
|
||||
last prepare; pass --auto-prepare to re-sync first. Otherwise the
|
||||
command runs from root's home directory. --guest-path overrides both.
|
||||
|
||||
Exit code of the guest command is propagated verbatim.
|
||||
`),
|
||||
|
|
@ -76,13 +77,14 @@ Exit code of the guest command is propagated verbatim.
|
|||
return fmt.Errorf("vm %q is not running (state: %s)", vm.Name, vm.State)
|
||||
}
|
||||
|
||||
// Resolve effective guest workspace path.
|
||||
// Resolve effective guest workspace path. Empty means "no
|
||||
// cd": run from the SSH session's default cwd ($HOME). We
|
||||
// only auto-cd when the user explicitly passed --guest-path
|
||||
// or the VM actually has a recorded workspace — otherwise
|
||||
// arbitrary VMs (no repo) would fail with cd errors.
|
||||
execGuestPath := strings.TrimSpace(guestPath)
|
||||
if execGuestPath == "" {
|
||||
execGuestPath = vm.Workspace.GuestPath
|
||||
}
|
||||
if execGuestPath == "" {
|
||||
execGuestPath = "/root/repo"
|
||||
execGuestPath = strings.TrimSpace(vm.Workspace.GuestPath)
|
||||
}
|
||||
|
||||
// Dirty-workspace check: compare stored HEAD with current host HEAD.
|
||||
|
|
@ -130,15 +132,18 @@ Exit code of the guest command is propagated verbatim.
|
|||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVar(&guestPath, "guest-path", "", "workspace directory in the guest (default: from last workspace prepare, or /root/repo)")
|
||||
cmd.Flags().StringVar(&guestPath, "guest-path", "", "workspace directory in the guest (default: from last workspace prepare; otherwise root's home)")
|
||||
cmd.Flags().BoolVar(&autoPrepare, "auto-prepare", false, "re-sync the workspace from the host repo before running if it's stale")
|
||||
_ = cmd.RegisterFlagCompletionFunc("guest-path", cobra.NoFileCompletions)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// buildVMExecScript returns the bash -lc argument that cd's into the
|
||||
// workspace and runs the command through mise exec when mise is
|
||||
// available, falling back to a plain exec if it's not. Each command
|
||||
// buildVMExecScript returns the bash -lc argument that runs the
|
||||
// command through mise exec when mise is available, falling back to a
|
||||
// plain exec if it's not. When guestPath is non-empty, the script
|
||||
// cd's into it first (workspace mode); when empty, the command runs
|
||||
// from the SSH session's default cwd so VMs without a prepared
|
||||
// workspace don't blow up on a non-existent /root/repo. Each command
|
||||
// argument is shell-quoted so spaces and special characters survive
|
||||
// the bash re-parse inside the -lc string.
|
||||
func buildVMExecScript(guestPath string, command []string) string {
|
||||
|
|
@ -147,12 +152,15 @@ func buildVMExecScript(guestPath string, command []string) string {
|
|||
parts[i] = shellQuote(a)
|
||||
}
|
||||
quotedCmd := strings.Join(parts, " ")
|
||||
return fmt.Sprintf(
|
||||
"cd %s && if command -v mise >/dev/null 2>&1; then mise exec -- %s; else %s; fi",
|
||||
shellQuote(guestPath),
|
||||
body := fmt.Sprintf(
|
||||
"if command -v mise >/dev/null 2>&1; then mise exec -- %s; else %s; fi",
|
||||
quotedCmd,
|
||||
quotedCmd,
|
||||
)
|
||||
if guestPath == "" {
|
||||
return body
|
||||
}
|
||||
return fmt.Sprintf("cd %s && %s", shellQuote(guestPath), body)
|
||||
}
|
||||
|
||||
// vmExecDirtyCheck compares the HEAD commit stored in the VM's
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue