wavetermdev/waveterm · error
failed to build shell command: %w
Error message
failed to build shell command: %w
What it means
SSHProcessController.Start builds the final shell command string from the stored CommandSpec via BuildShellCommand before handing it to the SSH session. If the CommandSpec is structurally invalid (BuildShellCommand returns an error), Start wraps and returns it as 'failed to build shell command'. The command never reaches the remote host.
Source
Thrown at pkg/genconn/ssh-impl.go:70
lock: &sync.Mutex{},
once: &sync.Once{},
cmdSpec: cmdSpec,
session: session,
}, nil
}
// Start begins execution of the command
func (s *SSHProcessController) Start() error {
s.lock.Lock()
defer s.lock.Unlock()
if s.started {
return fmt.Errorf("command already started")
}
fullCmd, err := BuildShellCommand(s.cmdSpec)
if err != nil {
return fmt.Errorf("failed to build shell command: %w", err)
}
// if stdout/stderr weren't piped, then session.stdout/stderr will be nil
// and the library guarantees that the outputs will be attached to io.Discard
// if stdin hasn't been piped, then session.stdin will be nil
// and the libary guarantees that it will be attached to an empty bytes.Buffer, which will produce an immediate EOF
// tl;dr we don't need to worry about hanging beause of long input or explicitly closing stdin
if err := s.session.Start(fullCmd); err != nil {
return fmt.Errorf("failed to start command: %w", err)
}
s.started = true
return nil
}
// Wait waits for the command to complete
func (s *SSHProcessController) Wait() error {
s.once.Do(func() {
s.waitErr = s.session.Wait()
})View on GitHub (pinned to a4447c1563)
Solutions
- Inspect the wrapped error from BuildShellCommand to see which spec field is invalid.
- Validate the CommandSpec before creating the controller (non-empty command, known shell).
- Fix the spec construction site (config file, JSON deserialization, or literal).
Example fix
// before
spec := genconn.CommandSpec{Shell: "powershell"} // on a linux remote
ctrl, _ := sshShellClient.MakeProcessController(spec)
err := ctrl.Start() // failed to build shell command
// after
spec := genconn.CommandSpec{Command: "ls -la", Shell: "bash"}
ctrl, err := sshShellClient.MakeProcessController(spec)
if err != nil { return err }
if err := ctrl.Start(); err != nil { return err } Defensive patterns
Strategy: validation
Validate before calling
func validSpec(s genconn.CommandSpec) error {
if s.Command == "" { return errors.New("CommandSpec.Command is empty") }
switch s.Shell { case "", "bash", "sh", "zsh", "cmd", "powershell": return nil
default: return fmt.Errorf("unsupported shell %q", s.Shell) }
}
// call before MakeSSHCmdClient/Start
if err := validSpec(spec); err != nil { return err } Try / catch
if err := ctrl.Start(); err != nil {
var buildErr error
errors.As(err, &buildErr) // log full wrapped chain
return fmt.Errorf("start failed (check CommandSpec): %w", err)
} Prevention
- Validate CommandSpec fields (non-empty Command, supported Shell) before constructing controllers.
- Never build CommandSpec by hand-editing raw strings; use constructors or validated config.
- Add a unit test covering every shell value your app can put into CommandSpec.
- Log the full wrapped error chain (%w / errors.Unwrap) to pinpoint the bad field.
When it happens
Trigger: Calling Start() on an SSHProcessController whose CommandSpec cannot be rendered by BuildShellCommand — e.g. an invalid command spec configuration passed to MakeSSHCmdClient/MakeProcessController (bad shell type, empty/invalid command field).
Common situations: Passing a CommandSpec with an unsupported shell value; constructing a spec programmatically with an empty Command; a spec deserialized from config/JSON missing required fields.
Related errors
- cannot parse connection name: %w
- --conn parameter is required
- invalid ssh remote name (%s): %w
- failed to build shell command: %w
- invalid format of user@host argument
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/36fb83a2e5983fba.
Report an issue: GitHub.