hashicorp/nomad · error
failed to marshal command: %W
Error message
failed to marshal command: %W
What it means
In api/allocations_exec.go:94, startConnection json.Marshal's the exec command struct before embedding it as a query parameter. Marshal of a plain struct with strings/bools essentially never fails, so this error is practically unreachable; if it fires, the ExecStreamingInput/command structure contains a value json cannot encode. Note the format verb is a (invalid) capitalized %W — it still behaves like %v, so the wrapped error is not unwrappable via errors.Is/As.
Source
Thrown at api/allocations_exec.go:94
func (s *execSession) startConnection() (*websocket.Conn, error) {
// First, attempt to connect to the node directly, but may fail due to network isolation
// and network errors. Fallback to using server-side forwarding instead.
nodeClient, err := s.client.GetNodeClientWithTimeout(s.alloc.NodeID, ClientConnTimeout, s.q)
if err == NodeDownErr {
return nil, NodeDownErr
}
q := s.q
if q == nil {
q = &QueryOptions{}
}
if q.Params == nil {
q.Params = make(map[string]string)
}
commandBytes, err := json.Marshal(s.command)
if err != nil {
return nil, fmt.Errorf("failed to marshal command: %W", err)
}
q.Params["tty"] = strconv.FormatBool(s.tty)
q.Params["task"] = s.task
q.Params["command"] = string(commandBytes)
reqPath := fmt.Sprintf("/v1/client/allocation/%s/exec", s.alloc.ID)
if s.action != "" {
q.Params["action"] = s.action
q.Params["allocID"] = s.alloc.ID
q.Params["group"] = s.alloc.TaskGroup
reqPath = fmt.Sprintf("/v1/job/%s/action", url.PathEscape(s.job))
}
var conn *websocket.Conn
if nodeClient != nil {
conn, _, _ = nodeClient.websocket(reqPath, q) //nolint:bodyclose // gorilla/websocket Dialer.DialContext() does not require the body to be closed.View on GitHub (pinned to 482b49bf1a)
Solutions
- Inspect the wrapped error to identify which field fails to marshal
- Ensure all fields of the exec command struct are JSON-encodable (strings, bools, slices)
- If you patched the package, revert the unsupported field or add a MarshalJSON method
- Report upstream if this occurs with an unmodified client — it indicates a version incompatibility
Example fix
// before (in a patched struct)
Command struct{ Args []string; Notify chan int `json:"notify"` }
// after: remove or encode the unmarshalable field
Command struct{ Args []string } Defensive patterns
Strategy: validation
Validate before calling
if _, err := json.Marshal(cmd); err != nil {
return fmt.Errorf("command not encodable: %w", err)
} Type guard
func jsonEncodable(v any) bool {
b, err := json.Marshal(v)
return err == nil && b != nil
} Try / catch
conn, err := s.startConnection()
if err != nil && strings.Contains(err.Error(), "failed to marshal command") {
return fmt.Errorf("client bug: exec command struct not JSON-encodable: %w", err)
} Prevention
- Keep exec command structs limited to JSON-native types
- Add marshal smoke tests if you vendor/patch the api package
- Prefer strings/bools/slices over custom types in command payloads
When it happens
Trigger: Calling Exec/ActionExec where s.command fails to JSON-marshal — only possible if the command struct is modified to include unsupported types (channels, funcs, cyclic data). Not triggerable with the standard library's command types.
Common situations: Forking/patching the Nomad API package and adding a field with an unsupported type to the exec command struct; building the command with a custom type lacking MarshalJSON.
Understand the failure class
Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.
Related errors
- unable to unmarshal response with status %d: %v
- failed to send input: %w
- websocket closed before receiving exit code: %w
- failed to decode log endpoint response as JSON: %q
- failed to marshal command into json: %v
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/2de237fca057e9fc.
Report an issue: GitHub.