dagger/dagger · error

unmarshal sdk as struct: %w

Error message

unmarshal sdk as struct: %w

What it means

For non-legacy configs, UnmarshalJSON decodes the sdk object through a type alias so encoding/json's default behavior applies to the struct fields. If that unmarshal fails — wrong JSON types for fields, e.g. a string where an object is expected — the error is wrapped as "unmarshal sdk as struct" so the caller knows the modern (object-shaped) sdk decode is what failed.

Source

Thrown at core/modules/config.go:262

	if len(data) == 0 {
		sdk.Source = ""
		return nil
	}

	// check if this is a legacy config, where sdk was a string
	if data[0] == '"' {
		var sdkRefStr string
		if err := json.Unmarshal(data, &sdkRefStr); err != nil {
			return fmt.Errorf("unmarshal sdk as string: %w", err)
		}
		*sdk = SDK{Source: sdkRefStr}
		return nil
	}

	type alias SDK // lets us use the default json unmashaler
	var tmp alias
	if err := json.Unmarshal(data, &tmp); err != nil {
		return fmt.Errorf("unmarshal sdk as struct: %w", err)
	}
	*sdk = SDK(tmp)
	return nil
}

func (modCfg *ModuleConfig) UnmarshalJSON(data []byte) error {
	if modCfg == nil {
		return fmt.Errorf("cannot unmarshal into nil %T", modCfg)
	}
	if len(data) == 0 {
		return nil
	}

	type alias ModuleConfig // lets us use the default json unmashaler
	var tmp struct {
		alias
		Runtime *SDK `json:"runtime,omitempty"`
	}

View on GitHub (pinned to 82ba2681db)

Solutions

  1. Check the sdk field shape in dagger.json: current format is an object like {"source": "python"}, legacy is a string
  2. Fix field types inside the sdk object to match SDK's JSON schema (string source, etc.)
  3. Validate the config with `jq . dagger.json` and by running a dagger command that loads the module
  4. Regenerate dagger.json with `dagger mod init` or `dagger mod sync` to get a canonical shape

Example fix

// dagger.json before
"sdk": { "source": 123 }
// after
"sdk": { "source": "python" }
Defensive patterns

Strategy: validation

Validate before calling

// check sdk is either a string (legacy) or an object with a string source
var probe struct {
	SDK json.RawMessage `json:"sdk"`
}
json.Unmarshal(raw, &probe)
var obj struct{ Source string `json:"source"` }
if err := json.Unmarshal(probe.SDK, &obj); err != nil {
	return fmt.Errorf("sdk must be a string or {\"source\": string}")
}

Try / catch

var cfg modules.ModuleConfig
if err := json.Unmarshal(raw, &cfg); err != nil {
	return fmt.Errorf("invalid sdk object in dagger.json: %w", err)
}

Prevention

When it happens

Trigger: A dagger.json where sdk is an object (or any non-string value) whose inner fields have mismatched types, e.g. "sdk": "python" going through the struct path after the alias unmarshal rejects it, or "sdk": {"source": 123} with a non-string source.

Common situations: Hand-edited configs with wrong field types; mixing legacy and current config shapes after an upgrade; tooling that writes sdk with the wrong JSON types.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of dagger/dagger@82ba2681db (2026-09-05). Data as JSON: /api/errors/f292ed9bae31d2fb. Report an issue: GitHub.