daemon: auto-trust mise configs on workspace prepare

vm run ./repo (and the explicit vm workspace prepare) imports the
host user's own checkout. Any .mise.toml that lands in the guest
would otherwise prompt on the first guest command — 'mise trust:
hash mismatch, run "mise trust"' — and stall what should be a
zero-friction sandbox launch. The repo just came from the host,
the guest is single-tenant root@<vm>.vm, the user already trusts
this checkout: auto-trust is the right default here.

After workspaceImportHook succeeds, run
  if command -v mise >/dev/null 2>&1; then
    mise trust --quiet --all <guest_path> || true
  fi
inside the guest. Best effort: a missing mise binary, a non-zero
exit, or a no-op trust all log at debug only and never fail
prepare. The path is shell-quoted via ws.ShellQuote so guest
paths with spaces or quotes don't break the argument.

Tests pin the script shape (command -v guard + --quiet --all flag
+ trailing `|| true`) and assert the script actually fires after
a successful import. A path with an apostrophe round-trips via
ws.ShellQuote without truncation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Thales Maciel 2026-04-26 23:08:41 -03:00
parent fa4292756d
commit c8637b0fe4
No known key found for this signature in database
GPG key ID: 33112E6833C34679
2 changed files with 126 additions and 4 deletions

View file

@ -184,6 +184,39 @@ func (s *WorkspaceService) PrepareVMWorkspace(ctx context.Context, params api.VM
return s.prepareVMWorkspaceGuestIO(ctx, vm, strings.TrimSpace(params.SourcePath), guestPath, branchName, fromRef, mode, params.IncludeUntracked)
}
// miseTrustGuestRepo runs `mise trust` against guestPath inside the
// guest so any .mise.toml / .tool-versions / mise.toml files in the
// imported repo become trusted without an interactive prompt. Best
// effort: a missing mise binary, a non-zero exit, or a trust that
// finds nothing all log at debug only and don't fail prepare.
//
// The guest is single-tenant root@<vm>.vm and the repo just came
// from the host user's own checkout, so auto-trust is safe in this
// context — the user has already validated the repo on the host.
func (s *WorkspaceService) miseTrustGuestRepo(ctx context.Context, client ws.GuestClient, guestPath string) {
script := miseTrustScript(guestPath)
if err := client.RunScript(ctx, script, miseTrustLogSink{}); err != nil && s.logger != nil {
s.logger.Debug("mise trust on imported workspace skipped", "guest_path", guestPath, "error", err.Error())
}
}
// miseTrustScript is the exact shell run inside the guest. Kept
// separate so a unit test can pin the string and confirm a future
// edit doesn't accidentally drop the `command -v` guard.
func miseTrustScript(guestPath string) string {
return fmt.Sprintf(
"if command -v mise >/dev/null 2>&1; then mise trust --quiet --all %s 2>/dev/null || true; fi\n",
ws.ShellQuote(guestPath),
)
}
// miseTrustLogSink discards anything mise wrote to stdout/stderr.
// We don't care about the output — success leaves mise silent and a
// failure is already covered by the err return path.
type miseTrustLogSink struct{}
func (miseTrustLogSink) Write(p []byte) (int, error) { return len(p), nil }
// prepareVMWorkspaceGuestIO performs the actual guest-side work:
// inspect the local repo, dial SSH, stream the tar. Called without
// holding the VM mutex.
@ -207,6 +240,13 @@ func (s *WorkspaceService) prepareVMWorkspaceGuestIO(ctx context.Context, vm mod
if err := s.workspaceImportHook(ctx, client, spec, guestPath, mode); err != nil {
return model.WorkspacePrepareResult{}, err
}
// Auto-trust mise configs in the imported repo. The guest is
// single-tenant (root@<vm>.vm), the repo just came from the
// host user's own checkout, and any .mise.toml landing in /root
// would otherwise prompt on the first guest command and stall a
// 'banger vm run ./repo -- <cmd>' invocation. Best-effort: a
// missing mise binary or a 'trust' that does nothing is fine.
s.miseTrustGuestRepo(ctx, client, guestPath)
return model.WorkspacePrepareResult{
VMID: vm.ID,
SourcePath: spec.SourcePath,