I ignore why it is like that, in particular whether it is happening due to a code line(s) in PyPSA or in the optimisation solver.
It's indeed different from what one would have programmed thanks to some algorithm, if one would have written a code to solve your problem, where at some point you would have an "if" choice that tests whether the battery is full or not, which would then immediately lead to filling up the battery, is there is excess electricity and the battery is not full.
Concerning the use of negative e_min_pu values, this is how I've decided to model the "grid" in many of my models.
If you allow both sale and purchase of electricity from the grid, you could either model "purchase from the grid" thanks to a generator with a marginal cost for every MWh delivered by the generator representing the grid, and then have a "store" that represents "sell to the grid" with a marginal cost (money is received when energy is sent to storage) for every MWh sent into the storage representing the grid. You don't want to represent the grid as "demand" element, even if it represents some sort of demand, as otherwise you have to send electricity to the grid. By modelling the grid as "store" and providing a monetary incentive to sent electricity to it, you can allow "free" energy not to be curtailed.
If your model allows sale and purchase at the same price, you could use a single "store" component that is allowed to have negative values (so that you can import from the grid even at the beginning of the year, when not enough has been sent to the grid yet). The reason that one would do that is to track easily throughout the year the cumulative amount of energy sent to or received from the grid, as can be seen on the graph below. Obviously, you can also do that with the first option (generator and store), but you would need a bit more code lines to trace something like the graph below:
Below is the text that I've put in the "stores.csv" file of my model:
name,bus,e_nom,e_initial,e_nom_extendable,capital_cost,e_min_pu
grid,grid,1000,0,True,0,-1
there is also a "stores-marginal_cost" containing the following first lines (just as an example with a different marginal cost for every time step):
,grid
0,61.45
1,65.73
2,64.96
3,60.47
4,52.5
5,48.98
6,48.95
...
In "links.csv", I have the following line for the link allowing to send energy between the grid and demand:
name,bus0,bus1,efficiency,p_nom,p_nom_extendable,marginal_cost,capital_cost,p_min_pu
grid,grid,demand,1,0,TRUE,0,834,-1
As you can see above, I have a put 0 for marginal cost for the import/export cable, and a capital_cost for the cost of installation of such a connection (only useful, if you want to optimise the cable size, and not a necessity even in that case). but more importantly, I've given a negative value for "p_min_pu" to allow electricity being sent back and forth between the grid and demand with a single link (two different links might create issues, where the model sends electricity through both cables at the same time, unless you put an efficiency below 1, maybe - haven't tried).