dagger/dagger · error

encode persisted service host socket %d: nil socket

Error message

encode persisted service host socket %d: nil socket

What it means

While encoding a service for persistence, one of the entries in svc.HostSockets is a nil *Socket. Host sockets record host=>container socket forwards and must carry Kind/Handle/URL data; a nil entry would produce a corrupt payload, so encoding aborts with the index of the offending socket.

Source

Thrown at core/service.go:154

		if err != nil {
			return dagql.PersistedObjectEncoding{}, err
		}
	}
	if svc.ModuleContext.Self() != nil {
		payload.ModuleContextResultID, err = encodePersistedObjectRef(cache, svc.ModuleContext, "service module context")
		if err != nil {
			return dagql.PersistedObjectEncoding{}, err
		}
	}
	if svc.TunnelUpstream.Self() != nil {
		payload.TunnelUpstreamResultID, err = encodePersistedObjectRef(cache, svc.TunnelUpstream, "service tunnel upstream")
		if err != nil {
			return dagql.PersistedObjectEncoding{}, err
		}
	}
	for i, sock := range svc.HostSockets {
		if sock == nil {
			return dagql.PersistedObjectEncoding{}, fmt.Errorf("encode persisted service host socket %d: nil socket", i)
		}
		payload.HostSockets = append(payload.HostSockets, persistedServiceHostSocket{
			Kind:           sock.Kind,
			Handle:         sock.Handle,
			URLVal:         sock.URLVal,
			PortForwardVal: sock.PortForwardVal,
			SourceClientID: sock.SourceClientID,
		})
	}
	enc, err := json.Marshal(payload)
	if err != nil {
		return dagql.PersistedObjectEncoding{}, fmt.Errorf("marshal persisted service payload: %w", err)
	}
	return encodePersistedObjectRawJSON(enc), nil
}

func (*Service) DecodePersistedObject(ctx context.Context, dag *dagql.Server, _ uint64, _ *dagql.ResultCall, payload json.RawMessage) (dagql.Typed, error) {
	var persisted persistedServicePayload

View on GitHub (pinned to 82ba2681db)

Solutions

  1. Find where the nil Socket was appended to HostSockets and skip or error at registration time instead.
  2. Filter out nil sockets before persisting: svc.HostSockets = slices.DeleteFunc(svc.HostSockets, func(s *Socket) bool { return s == nil }).
  3. Clear the stale cache entry and re-create the host socket tunnel.

Example fix

// before
svc.HostSockets = append(svc.HostSockets, sock) // sock may be nil
// after
if sock != nil {
    svc.HostSockets = append(svc.HostSockets, sock)
}
Defensive patterns

Strategy: validation

Validate before calling

for i, sock := range svc.HostSockets {
    if sock == nil {
        return fmt.Errorf("host socket %d is nil before persisting", i)
    }
}

Type guard

func hasNilSocket(svc *dagger.Service) bool {
    for _, s := range svc.HostSockets { if s == nil { return true } }
    return false
}

Try / catch

if err != nil && strings.Contains(err.Error(), "nil socket") {
    // drop nil sockets and re-persist, or recreate the host tunnel
}

Prevention

When it happens

Trigger: Calling EncodePersistedObject (or persisting any result referencing the service) when svc.HostSockets contains a nil element — e.g. a socket forward registered with a nil *Socket, or HostSockets populated from a deserialized/legacy payload containing null entries.

Common situations: Host socket tunnels (Container.WithExposedPort / host tunnel features) whose socket handles were not properly initialized; results decoded from older cache formats with null hostSocket entries then re-persisted.

Related errors


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