go-delve/delve · error

cannot use %s as 'substitutePath' of type {"from":string, "t

Error message

cannot use %s as 'substitutePath' of type {"from":string, "to":string}

What it means

Each element of the launch/attach config's substitutePath array must be a JSON object with string fields 'from' and 'to'. A JSON value of the wrong type (e.g. a string, number, or array) appeared where such an object was expected, so custom UnmarshalJSON returns this descriptive error instead of the raw Go type error.

Source

Thrown at service/dap/types.go:255

// For example, mapping with empty 'to' can be used to work with binaries with trimmed paths.
type SubstitutePath struct {
	// The local path to be replaced when passing paths to the debugger.
	From string `json:"from,omitempty"`
	// The remote path to be replaced when passing paths back to the client.
	To string `json:"to,omitempty"`
}

func (m *SubstitutePath) UnmarshalJSON(data []byte) error {
	// use custom unmarshal to check if both from/to are set.
	type tmpType struct {
		From *string
		To   *string
	}
	var tmp tmpType

	if err := json.Unmarshal(data, &tmp); err != nil {
		if _, ok := err.(*json.UnmarshalTypeError); ok {
			return fmt.Errorf(`cannot use %s as 'substitutePath' of type {"from":string, "to":string}`, data)
		}
		return err
	}
	if tmp.From == nil || tmp.To == nil {
		return errors.New("'substitutePath' requires both 'from' and 'to' entries")
	}
	*m = SubstitutePath{*tmp.From, *tmp.To}
	return nil
}

// AttachConfig is the collection of attach request attributes recognized by DAP implementation.
// 'processId' and 'waitFor' are mutually exclusive, and can't be specified at the same time.
type AttachConfig struct {
	// Acceptable values are:
	//   "local": attaches to the local process with the given ProcessID.
	//   "remote": expects the debugger to already be running to "attach" to an in-progress debug session.
	//
	// Default is "local".

View on GitHub (pinned to a23773e6c3)

Solutions

  1. Make every substitutePath entry an object: {"from":"<remote>","to":"<local>"}
  2. Ensure both 'from' and 'to' are present string values (missing fields give a related error)
  3. Validate launch.json against the DAP/delve schema in your editor
  4. Quote values as strings — numbers and booleans are rejected

Example fix

// before
"substitutePath": ["/remote/src"]
// after
"substitutePath": [{"from": "/remote/src", "to": "/local/src"}]
Defensive patterns

Strategy: validation

Validate before calling

func validSubstitutePath(sp []json.RawMessage) error {
  for _, r := range sp {
    var o map[string]any
    if err := json.Unmarshal(r, &o); err != nil { return err }
    f, okF := o["from"].(string); t, okT := o["to"].(string)
    if !okF || !okT { return fmt.Errorf("entry %s must be {from:string,to:string}", r) }
  }
  return nil
}

Type guard

func isSubstitutePathEntry(v any) bool {
  m, ok := v.(map[string]any); if !ok { return false }
  _, okF := m["from"].(string); _, okT := m["to"].(string)
  return okF && okT
}

Try / catch

if _, err := unmarshalLaunchAttachArgs(raw); err != nil { if strings.Contains(err.Error(), "substitutePath") { /* fix launch.json to object entries */ } return err }

Prevention

When it happens

Trigger: onLaunchRequest/onAttachRequest where substitutePath contains a non-object entry, e.g. "substitutePath": ["/a"] or [ {"from":1,"to":"/b"} ].

Common situations: Users copying old string-array substitutePath syntax from other debuggers, hand-edited launch.json, or schema-less editors not validating the object shape.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of go-delve/delve@a23773e6c3 (2026-08-31). Data as JSON: /api/errors/66d2eb976d0dfd9a. Report an issue: GitHub.