dagger/dagger · error
invalid JSON string
Error message
invalid JSON string
What it means
ID.UnmarshalJSON rejects any JSON payload that is not a quoted string (minimum 2 chars, starting and ending with a double quote). The library throws this because a Dagger ID must be a base64-encoded DAG proto serialized as a JSON string; non-string JSON (numbers, objects, null, or empty input) cannot be decoded.
Source
Thrown at dagql/call/id.go:591
proto, err := proto.MarshalOptions{Deterministic: true}.MarshalAppend(buf, dagPB)
if err != nil {
return "", fmt.Errorf("failed to marshal ID proto: %w", err)
}
return base64.StdEncoding.EncodeToString(proto), nil
}
func (id *ID) MarshalJSON() ([]byte, error) {
enc, err := id.Encode()
if err != nil {
return nil, err
}
return []byte(`"` + enc + `"`), nil
}
func (id *ID) UnmarshalJSON(data []byte) error {
if len(data) < 2 || data[0] != '"' || data[len(data)-1] != '"' {
return fmt.Errorf("invalid JSON string")
}
enc := string(data[1 : len(data)-1])
return id.Decode(enc)
}
// NOTE: use with caution, any mutations to the returned proto can corrupt the ID
func (id *ID) ToProto() (*callpbv1.DAG, error) {
if id == nil {
return &callpbv1.DAG{}, nil
}
if id.mode == idModeHandle {
if id.typ == nil {
return nil, fmt.Errorf("handle-form ID missing type")
}
return &callpbv1.DAG{
Value: &callpbv1.DAG_EngineResult{
EngineResult: &callpbv1.EngineResultRef{
ResultID: id.engineResultID,View on GitHub (pinned to 82ba2681db)
Solutions
- Ensure the JSON value for the ID field is a double-quoted base64 string, e.g. "dAG..."
- Coerce or guard nulls before unmarshaling: only call json.Unmarshal on payloads where the ID field is a string
- If the value may be absent, use a *ID (pointer) or json.RawMessage and unmarshal conditionally
- Verify the producer is serializing the ID via ID.MarshalJSON (which quotes it) and not writing raw bytes
Example fix
// before
data := []byte(`{"id": null}`)
var v struct{ ID dagql.ID }
json.Unmarshal(data, &v) // error: invalid JSON string
// after
data := []byte(`{"id": "<base64-id>"}`)
var v struct{ ID dagql.ID }
if err := json.Unmarshal(data, &v); err != nil { /* handle */ } Defensive patterns
Strategy: validation
Validate before calling
func validJSONStringID(b []byte) bool {
return len(b) >= 2 && b[0] == '"' && b[len(b)-1] == '"'
} Type guard
func isQuotedJSON(s string) bool {
var str string
return json.Unmarshal([]byte(s), &str) == nil
} Try / catch
var id dagql.ID
if err := json.Unmarshal(data, &id); err != nil {
if strings.Contains(err.Error(), "invalid JSON string") {
// payload's id field is not a string; handle fallback
}
return err
} Prevention
- Always serialize IDs through ID.MarshalJSON so they are quoted
- Use *ID for optional ID fields so null decodes without error
- Validate payloads with a schema that requires ID fields to be strings
- Roundtrip-test IDs (Encode -> Decode) in CI
When it happens
Trigger: Unmarshaling JSON into dagql/call.ID when the JSON field is not a quoted string — e.g. null, a number, an object/array, or a zero-length/one-char buffer.
Common situations: Feeding hand-crafted or partially serialized JSON into an ID field; a GraphQL/server layer sending null instead of a string ID; tests or clients constructing IDs incorrectly; schema drift where an ID field became nullable.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- load before: %w
- load after: %w
- decode persisted client filesync mirror payload: %w
- decode persisted generated code payload: %w
- decode persisted container withoutAnnotation lazy payload: %
AI-assisted analysis of dagger/dagger@82ba2681db (2026-09-05).
Data as JSON: /api/errors/bec11dd8a44a8abc.
Report an issue: GitHub.