close() - and other symbols provided by POSIX or ISO c/c++ may be a special case.
Somewhere in the later System V days of ELF, the OS (UNIX originally, later newlib and such) would make open, malloc, write, and so on into weak symbols (multiple definitions can be presents; first one found is used) and the actual implementation, _open, _malloc, write_, _close and a few hundred of their closest friends, would be normal strong symbols just by having a file of weak symbols (foo(int a, char* b, void* c);) that immediately did (foo{return _foo(a, b, c);}) This interposing layer allowed application devs to easily replace symbols in case you needed to shim something in, add something for profiling, interject behaviour or so on. It was a really nifty feature if you knew about it, understood it, and used it for good. Inevitably, someone would write a C file that didn't include anything that got appropriate prototypes, then write "int close = 3;" or something. You can see what's coming can't you? It was totally legal by the standard, but it was not pretty. There are remnants of that explained at
https://www.embecosm.com/appnotes/ean9/ ... pace_reent or
https://stackoverflow.com/questions/262 ... rnal-names
It's likely that this was convention was picked up in the code that became the ESP-IDF OS.
That wouldn't explain the linker not fussing about multiple definitions of the same global. That seems like it should get called as a foul. With -fsymbol-functions and the recent change in the language spec preferring .comm to .globl for symbols and Arduino's bizarre handling redinitions by the nature of it declaring .ino files already processed, and C++ operator overloading, it's getting increasingly difficult to tell which symbol is actually used in any given context.
None of this directly applies to OP's case. It's just saying that in many similar cases, this is actually intentionally chosen and desirable. In this case I'd share the expectation and the surprise.